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.txtが呼び出された直後にディスクに表示されますが、スクリプトが終了するまでopen()サイズは維持されます0B
  • 追加するtime.sleep(5)と、期待どおりに(または必要に応じて)動作し始めます

私はすでに見つけました:

  • os.fsync()しかし、うまくいかないようです(からの結果は空use_fileであるかのようです)C:\foo.txt
  • (ファイルを開くとき)の使用buffering=(1<<20)も機能しないようです

私はますますこの行動に興味があります。

質問:

  • Pythonは操作をバックグラウンドにフォークしますか? 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 に送信します。OSがデータをバッファリングするため、必ずしもデータをディスクに書き込むわけではありません。あなたが正しく解決したように、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()

次に、ディスク上のファイルを見て、データがありますか?

于 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 で少なくとも 1 つのスレッドで失敗します。close_fdsPython 3.2 および 3.3 では、リダイレクトを使用しない場合の引数 toのデフォルト値が に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 に答える