在我的应用程序中,我使用多处理模块中的管道来在python进程之间进行通信。 最近我发现了一种奇怪的行为,这取决于我通过它们发送的数据的大小。 根据python文档,这些管道基于连接,并且应该以异步方式运行,但有时它们会在发送时陷入困境。如果我在每个连接中启用全双工,一切正常,即使我没有使用连接进行发送和收听。 任何人都可以解释这种行为吗?
代码(这不是我的生产代码,它只是说明了我的意思):
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()
答案 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
类可能会为你完成所有这些,但我以前从未使用它,所以你必须做一些实验。