为何在多个Popen子进程中会出现通信死锁?
在Python 2.7.3版本中,下面的问题并不存在。但是在我的机器上(64位Mac OSX 10.7.3),Python 2.7.1和Python 2.6版本中都出现了这个问题。这段代码我最终会分发出去,所以我想知道有没有什么方法可以完成这个任务,而不那么依赖于Python的版本。
我需要同时打开多个子进程,并向每个进程写入STDIN数据。通常,我会使用Popen.communicate
方法来做到这一点。然而,当我同时打开多个进程时,communicate
就会出现死锁。
import subprocess
cmd = ["grep", "hello"]
processes = [subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for _ in range(2)]
for p in processes:
print p.communicate("hello world\ngoodbye world\n")
如果我把进程的数量改成for _ in range(1)
,输出就会如预期那样:
('hello world\n', '')
但是,当进程数量变成两个(for _ in range(2)
)时,进程就会无限期地阻塞。我尝试了手动写入stdin的替代方法:
for p in processes:
p.stdin.write("hello world\ngoodbye world\n")
但即使这样,尝试从进程中读取数据(比如p.stdout.read()
)仍然会导致死锁。
起初,这个问题似乎与此有关,但它指出这个问题发生在使用多个线程时,并且死锁的情况非常少见(而在这里,它总是发生)。有没有办法让这个在2.7.3之前的Python版本中工作呢?
1 个回答
我花了一些时间才找到这个问题的解决办法。(我之前遇到过类似的问题,以为自己知道答案,结果错了。)
这个问题(以及2.7.3的修复方法)在这里有详细说明:
http://bugs.python.org/issue12786
问题的关键在于,PIPE会被子进程继承。解决办法是在调用Popen时加上'close_fds=True'。
processes = [subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,close_fds=True)
for _ in range(2)]
如果这样做导致你想要重用的其他文件描述符出现问题(如果这是一个简化的例子),其实你可以按照子进程创建的反向顺序来使用wait()/communicate(),这样似乎也能正常工作。
也就是说,不要这样:
for p in processes:
print p.communicate("hello world\ngoodbye world\n")
而是这样:
while processes:
print processes.pop().communicate("hello world\ngoodbye world\n")
(或者,我想你也可以在现有循环之前先用'processes.reverse()'来反转一下列表。)