2017-02-03 18 views
1

私はmultiprocessingパッケージを使用して、ファイルを同時に読み込み、いくつかのデータ転送の後にそのファイルの一部を上書きしようとしています。私はそれは少し抽象的だと理解していますが、私は自分のblocksync forkのスピードアップのための並行性のこの欠点を利用しています。Pythonマルチプロセッシングとファイルシーク

あなたは私のコードsnipplet見ることができます:

#!/usr/bin/python2 
import multiprocessing 
import sys 
import time 
blocksize=1024 

def do_open(f, mode): 
    f = open(f, mode) 
    f.seek(0, 2) 
    size = f.tell() 
    f.seek(0) 
    return f, size 

def pipe_getblocks(f, pipe, side): 
    print "Child file object ID: "+str(id(f)) 
    while True: 
     print "getblocks_seek_prev: "+str(f.tell()) 
     block = f.read(blocksize) 
     if not block: 
      break 
     print "getblocks_seek_next: "+str(f.tell()) 
     pipe.send(block) 

def pipe_server(dev): 
    f, size = do_open(dev, 'r+') 
    parent,child = multiprocessing.Pipe(False) 
    reader = multiprocessing.Process(target=pipe_getblocks, args=(f,child,"R")) 
    reader.daemon = True 
    reader.start() 
    child.close() 
    i = 0 
    print "Parent file object ID:"+str(id(f)) 
    while True: 
     try: 
      block = parent.recv() 
     except: 
      break 
     else: 
      print str(i)+":pseek: "+str(f.tell()/1024/1024) 
      f.seek(0,0) # This seek should not be see in the child subprocess... 
      i = i+1 

pipe_server("/root/random.img") 

基本的には、親プロセスは、それから読んでパイプを埋めるために子供を待つ必要があります。 f.seek(0,0)行に注意してください。ここでは、親と子それぞれがファイル内でどこを探すべきかという独自の考えを持っていることを確認するためにここに示します。言い換えれば、全く異なる2つのプロセスであることから、私はf.seekが親に対して行われても、その子には何の影響も及ぼさないと考えています。

しかし、上記のプログラムとして、この仮定が間違っているようだが、次のような出力を生成:あなたが見ることができるように

Child file object ID: 140374094691616 
getblocks_seek_prev: 0 
getblocks_seek_next: 1024 
... 
getblocks_seek_next: 15360 
getblocks_seek_prev: 15360 
getblocks_seek_next: 16384 
getblocks_seek_prev: 16384 
getblocks_seek_next: 17408 <-- past EOF! 
getblocks_seek_prev: 17408 <-- past EOF! 
getblocks_seek_next: 18432 <-- past EOF! 
getblocks_seek_prev: 18432 <-- past EOF! 
... 
Parent file object ID:140374094691616 
0:pseek: 0 
1:pseek: 0 
2:pseek: 0 
3:pseek: 0 
4:pseek: 0 
5:pseek: 0 
6:pseek: 0 
7:pseek: 0 
8:pseek: 0 
9:pseek: 0 
10:pseek: 0 
... 

を、子プロセスは、そのEOF過ぎて読んだり、まあ、それそれは実際にファイルの先頭から読み込んでいるので、だと思います。要するに、親のf.seek(0,0)は、それを認識せずに子プロセスに影響を与えているように見えます。

私は、ファイルオブジェクトが共有メモリに格納されていると仮定しているため、両方のプロセスが同じデータ/オブジェクトを変更しています。この考え方は、同一のデータを報告する親プロセスと子プロセスの両方から取得したid(f)によって確認されたようです。しかし、multiprocessingパッケージを使用しているときに、ファイルオブジェクトが共有メモリに保持されているという言及は見つかりませんでした。

私の質問は、それは期待された動作か、何か明白なものがないことですか?

答えて

2

Pythonはfork()を使用して子プロセスを開始しています。これにより、子プロセスは親からファイル記述子を継承します。彼らはファイルディスクリプタを共有しているので、同じシークオフセットも共有します。 fork(2)のmanページから:

子は、開いているファイルの親の集合 ディスクリプタのコピーを継承します。子の各ファイル記述子は、対応する ファイル記述子(open(2)を参照)を親の対応ファイル として参照します。これは、2つのファイル 記述子が、開いているファイルステータスフラグ、ファイルオフセット、および信号の 駆動I/O属性(fcntl(2)のF_SETOWNおよび F_SETSIGの説明を参照)を共有することを意味します。したがって、子プロセスにオブジェクトをクローニングし、基本的にだけである。UNIX上

パイソンfileオブジェクトは、ファイル記述子上に非常に薄いラッパー(the seek() method is just calling lseek(2) Pythonでの実装が現在fdno及び経路に関するいくつかのメタデータに要約)でありますファイルディスクリプタを送信します。

私が考えることができる最も簡単な解決策は、パスを子プロセスに渡し、各プロセスでファイルを別々に開くことです。 os.dupでトリッキーなことをやっているかもしれませんが、新しいプロセスを生成するときにいくつかのバイトを保存すること以外に利点があるかどうかはわかりません。

+0

非常に明確な説明、およびマニュアルページを引用した場合は+1です。私はかなりよくCベースのフォークを知っています。もしPythonがそれらの周りに薄いラッパーを提供していれば、これは基本的に**予想される動作です。 – shodanshok

+0

それについて二重に考えてみましょう:フォークでは、現在のオフセットは子によって継承されますが、ファイル記述子のコピーは独自のプライベートシーク位置を持つ必要がありますので、親のf.seek()を 'leaks'子プロセス... – shodanshok

+0

いいえ、両方のプロセスが同じファイル記述子を持っています。 FDは、ファイルと位置を表すOS提供のトークンです。両方のプロセスは同じトークンになります。その結果、両方のプロセスが基礎となるFDの変更を見ることになります。 FDは、libc/Python /何かを変更できる構造体ではなく、カーネル内の配列にマップされる整数だけです。 –

1

私は、子プロセスごとに別々のオープンを行いたいと思います。ファイルオブジェクトを引数として渡す代わりに、パスをファイルに渡してから、マルチプロセッシング呼び出し関数内で開きます。

+0

これは可能な回避策です。ありがとうございます。しかし、私はもっと興味があります**なぜ**それが起こっている場合、これは期待された動作です。 – shodanshok

関連する問題