我创建大文件和我的Python脚本(超过1GB
,居然还有人8)。 紧接着我创造他们我要创建过程中,将使用这些文件。
该脚本的样子:
# This is more complex function, but it basically does this:
def use_file():
subprocess.call(['C:\\use_file', 'C:\\foo.txt']);
f = open( 'C:\\foo.txt', 'wb')
for i in 10000:
f.write( one_MB_chunk)
f.flush()
os.fsync( f.fileno())
f.close()
time.sleep(5) # With this line added it just works fine
t = threading.Thread( target=use_file)
t.start()
但是,应用程序use_file
的作用就像foo.txt
是空的。 有一些奇怪的事情怎么回事:
- 如果我执行
C:\use_file C:\foo.txt
控制台(脚本结束后)我得到正确的结果 - 如果我手动执行
use_file()
在另一个Python控制台我得到正确的结果 -
C:\foo.txt
是上盘之后可见open()
被调用,但仍尺寸0B
直到脚本结束 - 如果我添加
time.sleep(5)
它只是开始按预期工作(或者说必须)
我已经发现:
-
os.fsync()
但它似乎没有工作(从结果use_file
仿佛是C:\foo.txt
是空的) - 使用
buffering=(1<<20)
开口文件时)似乎没有任何工作
我对这种行为越来越好奇。
问题:
- 难道蟒蛇叉
close()
操作为背景? 这哪里是记录? - 如何来解决这个身边?
- 我缺少的东西吗?
- 加入后
sleep
:是一个Windows / Python的错误吗?
注:(对于有些不对劲与对方的情况下)应用程序use_data
用途:
handle = CreateFile("foo.txt", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL);
size = GetFileSize(handle, NULL)
然后处理size
从字节foo.txt
。
f.close()
调用f.flush()
将数据发送到OS。 这并不一定是数据写入磁盘,因为操作系统对其进行缓冲。 当你正确地制定出来,如果要强制操作系统将其写入到磁盘,您需要os.fsync()
你有没有考虑干脆直接通过管道将数据转换成use_file
?
编辑:你说os.fsync()
“不工作”。 为了澄清,如果你这样做
f = open(...)
# write data to f
f.flush()
os.fsync(f.fileno())
f.close()
import pdb; pdb.set_trace()
再看看磁盘上的文件,它有数据吗?
编辑:具体到Python 3.x的信息更新
有一个超级老bug报告在讨论一个suspiciosly类似的问题https://bugs.python.org/issue4944 。 我做了一个小测试,显示错误: https://gist.github.com/estyrke/c2f5d88156dcffadbf38
在错误链接入门用户从一个eryksun解释精彩上面之后,我现在明白为什么会这样,是不是本身的错误每。 在Windows创建一个子进程,默认情况下它继承了所有打开的文件从父进程句柄。 所以你看到的可能是真正共享冲突,因为你想在子进程读取该文件是开放的,通过继承的手柄在另一个子进程写。 事件的可能的序列导致该(使用在高于主旨的再现为例):
Thread 1 opens file 1 for writing
Thread 2 opens file 2 for writing
Thread 2 closes file 2
Thread 2 launches child 2
-> Inherits the file handle from file 1, still open with write access
Thread 1 closes file 1
Thread 1 launches child 1
-> Now it can't open file 1, because the handle is still open in child 2
Child 2 exits
-> Last handle to file 1 closed
Child 1 exits
当我编译简单的C子程序和我的机器上运行脚本,它失败在大多数使用Python 2.7.8时的线程中的至少一个。 与Python 3.2和3.3不重定向测试脚本不会失败,因为默认值close_fds
参数subprocess.call
现在True
当不使用重定向。 其他使用测试脚本重定向仍然未能在这些版本中。 在Python 3.4两种测试成功,因为PEP 446,这使得所有的文件处理非继承默认的。
结论
从Python中的线程产卵一个子进程意味着孩子将继承所有打开的文件句柄,甚至从其他线程不是一个地方的孩子产生了。 这是,至少对我来说,不是特别直观。
可能的解决方案:
- 升级到Python 3.4,其中文件句柄是默认情况下不继承。
- 通过
close_fds=True
来subprocess.call
到完全禁用继承(这是在Python 3.x的默认值)。 不过,请注意,这阻止了子进程的标准输入/输出/错误的重定向。 - 确保所有文件产生新的进程之前被关闭。
- 使用
os.open
与打开文件os.O_NOINHERIT
在Windows标志。 使用WIN32API来代替。 传递一个NULL指针为lpSecurityAttributes
参数也防止继承描述符:
from contextlib import contextmanager import win32file @contextmanager def winfile(filename): try: h = win32file.CreateFile(filename, win32file.GENERIC_WRITE, 0, None, win32file.CREATE_ALWAYS, 0, 0) yield h finally: win32file.CloseHandle(h) with winfile(tempfilename) as infile: win32file.WriteFile(infile, data)