14

我正在用我的 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.txtopen()在被调用后立即在磁盘上可见,但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.

4

2 回答 2

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

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

于 2012-12-07T11:25:10.140 回答
6

编辑:更新了特定于 Python 3.x 的信息

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

在上面的错误链接中得到用户 eryksun 的精彩解释后,我现在明白为什么会发生这种情况,而且它本身并不是一个错误。在 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参数的默认值subprocess.call现在True是不使用重定向的时候。使用重定向的其他测试脚本在这些版本中仍然失败。在 Python 3.4 中,两个测试都成功了,因为 PEP 446 默认情况下使所有文件句柄都不可继承。

结论

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

可能的解决方案:

  • 升级到 Python 3.4,默认情况下文件句柄是不可继承的。
  • 传递close_fds=True给以subprocess.call完全禁用继承(这是 Python 3.x 中的默认设置)。请注意,这会阻止子进程的标准输入/输出/错误的重定向。
  • 确保在生成新进程之前关闭所有文件。
  • 用于在 Windows 上os.open打开带有os.O_NOINHERIT标志的文件。
    • tempfile.mkstemp也使用这个标志。
  • 请改用 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)
    
于 2015-03-25T07:00:21.707 回答