大文件调用close()后不立即刷新到磁盘?

我用我的Python脚本创build大文件(超过1GB ,实际上有8个)。 我创build它们后,我必须创build将使用这些文件的过程。

该脚本如下所示:

 # 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 (脚本完成后),我会得到正确的结果
  • 如果我在另一个python控制台手动执行use_file() ,我会得到正确的结果
  • 在调用open()之后, C:\foo.txt在磁盘上可见,但保持大小为0B直到脚本结束
  • 如果我添加time.sleep(5)它只是开始按预期工作(或者需要)

我已经发现:

  • os.fsync()但它似乎不工作(从use_file结果就好像C:\foo.txt是空的)
  • 使用buffering=(1<<20) (打开文件时)似乎也不起作用

我对这种行为越来越好奇。

问题:

  • python fork close()操作到后台? 这在哪里logging?
  • 如何解决这个问题?
  • 我错过了什么吗?
  • 添加sleep :是一个Windows / Python的错误?

注意:(对于另一端有问题的情况)应用程序use_data使用:

 handle = CreateFile("foo.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); size = GetFileSize(handle, NULL) 

然后从foo.txt处理size字节。

f.close()调用f.flush() ,将数据发送到操作系统。 这不一定会将数据写入磁盘,因为操作系统缓冲它。 正如你正确的解决,如果你想强制操作系统把它写入磁盘,你需要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的信息

https://bugs.python.org/issue4944上有一个超级老的bug报告,讨论了一个类似的疑似问题&#x3002; 我做了一个小测试,显示该错误: https : //gist.github.com/estyrke/c2f5d88156dcffadbf38

在用户eryksun上面的错误链接得到了一个很好的解释后,我现在明白了为什么会发生这种情况,而且这本身并不是一个bug。 在Windows上创建子进程时,默认情况下它会继承父进程中的所有打开文件句柄。 所以你看到的可能是实际上是一个共享冲突,因为你正试图读取子进程中的文件是打开的,通过在另一个子进程中的继承的句柄来写。 导致这种情况的一系列可能的事件(使用上面Gist的复制示例):

 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参数的默认值现在为True 。 其他使用重定向的测试脚本在这些版本中仍然失败。 在Python 3.4中,两个测试都成功了,因为PEP 446使得所有文件句柄在默认情况下都是不可继承的。

结论

从Python中的线程产生子进程意味着子进程继承所有打开的文件句柄,甚至是从子进程的其他线程继承。 至少对我而言,这并不是特别直观。

可能的解决方案:

  • 升级到Python 3.4,默认情况下文件句柄是不可继承的。
  • close_fds=True传递close_fds=True以完全禁用继承(这是Python 3.x中的默认设置)。 请注意,这可以防止子进程的标准输入/输出/错误的重定向。
  • 确保所有文件在产生新进程之前都关闭。
  • 使用os.open在Windows上使用os.O_NOINHERIT标志打开文件。
    • tempfile.mkstemp也使用这个标志。
  • 改用win32api。 传递lpSecurityAttributes参数的NULL指针也会阻止继承描述符:

     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)