python管道的同步/异步行为

时间:2013-04-21 14:41:28

标签: python linux multiprocessing pipe

在我的应用程序中,我使用多处理模块中的管道来在python进程之间进行通信。 最近我发现了一种奇怪的行为,这取决于我通过它们发送的数据的大小。 根据python文档,这些管道基于连接,并且应该以异步方式运行,但有时它们会在发送时陷入困境。如果我在每个连接中启用全双工,一切正常,即使我没有使用连接进行发送和收听。 任何人都可以解释这种行为吗?

  1. 100个浮点数,全双工禁用
    代码工作,利用异步性。
  2. 100个浮点数,全双工启用
    这个例子按预期工作正常。
  3. 10000个浮点数,全双工禁用
    即使数据较小,执行也会被永久封锁。
  4. 10000个浮点数,全双工启用 再好了。
  5. 代码(这不是我的生产代码,它只是说明了我的意思):

    from collections import deque
    from multiprocessing import Process, Pipe
    from numpy.random import randn
    from os import getpid
    
    PROC_NR = 4
    DATA_POINTS = 100
    # DATA_POINTS = 10000
    
    
    def arg_passer(pipe_in, pipe_out, list_):
        my_pid = getpid()
        print "{}: Before send".format(my_pid)
        pipe_out.send(list_)
        print "{}: After send, before recv".format(my_pid)
        buf = pipe_in.recv()
        print "{}: After recv".format(my_pid)
    
    
    if __name__ == "__main__":
        pipes = [Pipe(False) for _ in range(PROC_NR)]
        # pipes = [Pipe(True) for _ in range(PROC_NR)]
        pipes_in = deque(p[0] for p in pipes)
        pipes_out = deque(p[1] for p in pipes)
        pipes_in.rotate(1)
        pipes_out.rotate(-1)
    
        data = [randn(DATA_POINTS) for foo in xrange(PROC_NR)]
        processes = [Process(target=arg_passer, args=(pipes_in[foo], pipes_out[foo], data[foo]))
                     for foo in xrange(PROC_NR)]
    
        for proc in processes:
            proc.start()
    
        for proc in processes:
            proc.join()
    

1 个答案:

答案 0 :(得分:9)

首先,值得注意的是multiprocessing.Pipe类的实现......

def Pipe(duplex=True):
    '''
    Returns pair of connection objects at either end of a pipe
    '''
    if duplex:
        s1, s2 = socket.socketpair()
        s1.setblocking(True)
        s2.setblocking(True)
        c1 = _multiprocessing.Connection(os.dup(s1.fileno()))
        c2 = _multiprocessing.Connection(os.dup(s2.fileno()))
        s1.close()
        s2.close()
    else:
        fd1, fd2 = os.pipe()
        c1 = _multiprocessing.Connection(fd1, writable=False)
        c2 = _multiprocessing.Connection(fd2, readable=False)

    return c1, c2

不同之处在于半双工“管道”使用anonymous pipe,但全双工“管道”实际上使用Unix domain socket,因为匿名管道本质上是单向的。

在这种情况下,我不确定你所说的“异步”是什么意思。如果您的意思是“非阻塞I / O”,那么值得注意的是,默认情况下两个实现都使用阻塞I / O.


其次,值得注意您尝试发送的数据的腌制大小...

>>> from numpy.random import randn
>>> from cPickle import dumps
>>> len(dumps(randn(100)))
2479
>>> len(dumps(randn(10000)))
237154

第三,来自pipe(7)联机帮助页......

  

管道容量

     

管道容量有限。如果管道已满,则写入(2)将阻塞   或者失败,具体取决于是否设置了O_NONBLOCK标志(见下文)。不同   实现对管道容量有不同的限制。应用应该   不依赖于特定的容量:应该设计一个应用程序,以便a   读取过程一旦可用就会消耗数据,以便进行写入过程   不会被阻止。

     

在2.6.11之前的Linux版本中,管道的容量与系统相同   页面大小(例如,i386上的4096字节)。从Linux 2.6.11开始,管道容量就是   65536字节。


因此,实际上,您已经创建了一个死锁,其中所有子进程都在pipe_out.send()调用上被阻塞,并且它们都不能从其他进程接收任何数据,因为您发送了所有237,154字节一次命中中的数据,填充了65,536字节的缓冲区。

你可能只是想使用Unix域套接字版本,但它目前工作的唯一原因是它有一个比管道更大的缓冲区大小,你会发现如果你增加了它,解决方案也会失败DATA_POINTS到100,000的数量。

“快速n'脏黑客”解决方案是将数据分解为更小的块以便发送,但依靠特定大小的缓冲区并不是一种好的做法。

更好的解决方案是在pipe_out.send()调用上使用非阻塞I / O,尽管我对multiprocessing模块不够熟悉,无法确定使用该模块实现它的最佳方法模块。

伪代码将沿着......

while 1:
    if we have sent all data and received all data:
        break
    send as much data as we can without blocking
    receive as much data as we can without blocking
    if we didn't send or receive anything in this iteration:
        sleep for a bit so we don't waste CPU time
        continue

...或者您可以使用Python select模块避免长时间睡眠,但是,再次将其与multiprocessing.Pipe集成可能会非常棘手。

multiprocessing.Queue类可能会为你完成所有这些,但我以前从未使用它,所以你必须做一些实验。