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

时间:2012-12-07 11:12:53

标签: python windows io python-3.x large-files

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

我已经找到了:

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

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

问题:

  • python fork 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个字节。

2 个答案:

答案 0 :(得分:10)

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()

然后查看磁盘上的文件,它是否有数据?

答案 1 :(得分:6)

编辑:使用特定于Python 3.x的信息进行更新

有一个超级旧的错误报告在https://bugs.python.org/issue4944讨论了一个可疑的类似问题。我做了一个小测试,显示了错误:https://gist.github.com/estyrke/c2f5d88156dcffadbf38

在上面的bug链接中获得用户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的{​​{1}}参数的默认值现在是subprocess.call。在这些版本中,使用重定向的其他测试脚本仍然失败。在Python 3.4中,两个测试都成功,因为PEP 446默认情况下使所有文件句柄都不可继承。

结论

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

可能的解决方案:

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

    lpSecurityAttributes