Python子进程在接收stdin EOF时出现神秘延迟
我把我在应用程序中遇到的问题简化成了下面这个测试案例。在这段代码里,一个父进程同时启动了两个(你可以启动更多)子进程,这些子进程从父进程那里通过标准输入读取一条大消息,然后睡眠5秒钟,最后再写点东西回来。不过,代码的执行时间却意外地变成了10秒,而不是预期的5秒。
如果你把verbose=True设置为真,你会发现那个慢吞吞的子进程接收到了大部分消息,然后在等待最后3个字符的到来——它没有检测到管道已经关闭。此外,如果我干脆不对第二个进程做任何操作(doreturn=True),第一个进程将永远看不到结束信号(EOF)。
有没有人知道这是怎么回事?下面是一些示例输出。提前谢谢你。
from subprocess import *
from threading import *
from time import *
from traceback import *
import sys
verbose = False
doreturn = False
msg = (20*4096+3)*'a'
def elapsed(): return '%7.3f' % (time() - start)
if sys.argv[1:]:
start = float(sys.argv[2])
if verbose:
for chunk in iter(lambda: sys.stdin.read(4096), ''):
print >> sys.stderr, '..', time(), sys.argv[1], 'read', len(chunk)
else:
sys.stdin.read()
print >> sys.stderr, elapsed(), '..', sys.argv[1], 'done reading'
sleep(5)
print msg
else:
start = time()
def go(i):
print elapsed(), i, 'starting'
p = Popen(['python','stuckproc.py',str(i), str(start)], stdin=PIPE, stdout=PIPE)
if doreturn and i == 1: return
print elapsed(), i, 'writing'
p.stdin.write(msg)
print elapsed(), i, 'closing'
p.stdin.close()
print elapsed(), i, 'reading'
p.stdout.read()
print elapsed(), i, 'done'
ts = [Thread(target=go, args=(i,)) for i in xrange(2)]
for t in ts: t.start()
for t in ts: t.join()
示例输出:
0.001 0 starting
0.003 1 starting
0.005 0 writing
0.016 1 writing
0.093 0 closing
0.093 0 reading
0.094 1 closing
0.094 1 reading
0.098 .. 1 done reading
5.103 1 done
5.108 .. 0 done reading
10.113 0 done
我使用的是Python 2.6.5,如果这有影响的话。
相关问题:
1 个回答
6
经过很长时间的摸索,我终于搞明白了,灵感来自于这篇帖子中的一句话:
查看pipe(7)中的“I/O on Pipes and FIFOs”部分(可以用命令“man 7 pipe”查看)
“如果所有指向管道写入端的文件描述符都已经关闭,那么尝试从管道读取数据时会看到文件结束(read(2)会返回0)。”
我本该知道这一点,但当时没想到,这和Python没有直接关系。发生的事情是:子进程在相互的管道中打开了(写入)文件描述符。只要管道中还有打开的写入文件描述符,读取者就不会看到文件结束。
举个例子:
p1=Popen(..., stdin=PIPE, ...) # creates a pipe the parent process can write to
p2=Popen(...) # inherits the writer FD - as long as p2 exists, p1 won't see EOF
结果发现,Popen有一个close_fds参数,所以解决办法就是把close_fds=True传进去。事后看来这一切都简单明了,但还是浪费了不少时间。