2017-02-03 170 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