2017-02-03 56 views
1

我試圖使用multiprocessing包來併發讀取文件並在某些數據轉換後覆蓋(部分)文件。我知道這似乎有點抽象,但我有一個使用這種併發性扭結來加速我自己的blocksync forkPython多處理和文件搜索

下面你可以找到我的代碼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") 

基本上,父進程應該等待孩子來填充管,然後從中讀出。請注意0​​這一行:我把它放在這裏來驗證父母和孩子各自都有自己的想法,在哪裏尋找文件。換句話說,完全是兩個不同的過程,我期望父母完成的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)手冊頁:

的孩子繼承了打開的文件 描述符的父母的一套複印件。子項中的每個文件描述符與父項中的對應文件 描述符指向相同的 打開文件描述(請參閱open(2))。這意味着兩個文件 描述符共享打開的文件狀態標誌,文件偏移量和信號驅動的I/O屬性(請參閱fcntl(2)中F_SETOWN和 F_SETSIG的描述)。在UNIX

的Python file對象是在文件描述符非常薄的包裝(在Python實施目前歸結爲一個fdno和有關路徑的一些元數據; the seek() method is just calling lseek(2)),所以克隆對象到子過程基本上只是發送文件描述符。

我能想到的最簡單的解決方案是將路徑傳遞給子進程並分別在每個進程中打開文件。你也許可以用os.dup做一些棘手的事情,但我不確定在產生新進程時,除了保存一些字節之外,還有什麼好處。

+0

非常明確的解釋,和引用手冊頁+1。我知道基於C的分支很好,因此,如果Python只提供一個簡單的包裝器,這基本上是**期望的行爲。 – shodanshok

+0

雙重思考:用fork,當前的偏移量是由孩子繼承的,但其文件描述符副本應該有它自己的私有查找位置,所以我不確定在分支'泄漏'後如何完成父級的f.seek()子進程... – shodanshok

+0

不,兩個進程都具有相同的文件描述符。 FD是操作系統提供的代表文件和位置的令牌;兩個過程最終都有相同的標記,所以最終都會看到底層FD的任何更改。 FD不是一個libc/Python /可以改變的結構,它們只是映射到內核數組中的整數。 –

1

我想你想爲每個子進程單獨打開。不要將文件對象作爲參數傳遞,而應嘗試將路徑傳遞給文件,然後在多處理函數調用的函數內部打開。

+0

是的,這是一個可能的解決方法,謝謝。但是,如果發生這種情況,我對**爲什麼更感興趣,並且這是預期的行爲。 – shodanshok