让我们考虑以下Python代码,由Linux系统上的cpython执行(警告:它将尝试创建或覆盖/tmp/first
,/tmp/second
和/tmp/third
中的文件
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import subprocess
import os
import sys
import threading
class ThreadizedPopen(threading.Thread):
def __init__(self, command, stdin_name, stdout_name):
super(ThreadizedPopen, self).__init__()
self.command = command
self.stdin_name = stdin_name
self.stdout_name = stdout_name
self.returncode = None
def run(self):
with open(self.stdin_name, 'rb') as fin:
with open(self.stdout_name, 'wb') as fout:
popen = subprocess.Popen(self.command, stdin=fin, stdout=fout, stderr=None)
popen.communicate()
self.returncode = popen.returncode
def main():
os.system('mkfifo /tmp/first')
os.system('mkfifo /tmp/second')
os.system('mkfifo /tmp/third')
popen1 = ThreadizedPopen(['cat'], '/tmp/first', '/tmp/second')
popen2 = ThreadizedPopen(['cat'], '/tmp/second', '/tmp/third')
popen1.start()
popen2.start()
with open('/tmp/third') as fin:
print fin.read()
popen1.join()
popen2.join()
if __name__ == '__main__':
main()
然后我执行它,在另一个shell上,我在/tmp/first
中写了一些东西(比如echo test > /tmp/first
)。我希望Python程序能够快速退出并打印出我送到第一个FIFO的相同内容。
理论上应该发生的是,我在/tmp/first
中编写的字符串被我的程序生成的两个cat
进程复制到其他两个FIFO,然后被主Python程序选中写在它的标准。一旦每个cat
进程完成,它应该关闭其写入FIFO的末尾,使相应的读取结束返回EOF并触发后续cat
进程的终止。使用strace
查看程序会发现测试字符串已通过所有三个FIFO正确复制,并由主Python程序读取。第一个FIFO也正确关闭(第一个cat
进程与其管理器Python线程一起退出)。但是,第二个cat
进程停留在read()
调用中,期望来自其读取FIFO的数据。
我不明白为什么会这样。从pipe(t)
man page(据我所知,也涵盖了这种FIFO)来看,一旦写入结束(及其所有重复)被关闭,FIFO上的读取就会返回EOF。根据{{1}},这似乎是跟踪(特别是strace
进程已经死了,因此它的所有文件描述符都被关闭;它的管理线程也关闭了它的描述符,我可以看到它cat
输出。)
你能告诉我为什么会这样吗?如果它有用,我可以发布strace
输出。
答案 0 :(得分:1)
我找到了this question
并简单地将close_fds=True
添加到您的subprocess
电话中。您的代码现在显示为:
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import subprocess
import os
import sys
import threading
class ThreadizedPopen(threading.Thread):
def __init__(self, command, stdin_name, stdout_name):
super(ThreadizedPopen, self).__init__()
self.command = command
self.stdin_name = stdin_name
self.stdout_name = stdout_name
self.returncode = None
def run(self):
with open(self.stdin_name, 'rb') as fin:
with open(self.stdout_name, 'wb') as fout:
popen = subprocess.Popen(self.command, stdin=fin, stdout=fout, stderr=None, close_fds=True)
popen.communicate()
self.returncode = popen.returncode
def main():
os.system('mkfifo /tmp/first')
os.system('mkfifo /tmp/second')
os.system('mkfifo /tmp/third')
popen1 = ThreadizedPopen(['cat'], '/tmp/first', '/tmp/second')
popen2 = ThreadizedPopen(['cat'], '/tmp/second', '/tmp/third')
popen1.start()
popen2.start()
with open('/tmp/third') as fin:
print fin.read()
popen1.join()
popen2.join()
if __name__ == '__main__':
main()
我将您的代码放在名为fifo_issue.py
的脚本中,并在终端中运行。脚本正如您所期望的那样空闲(忽略mkfifo: cannot create fifo
):
$ python fifo_issue.py
mkfifo: cannot create fifo ‘/tmp/first’: File exists
mkfifo: cannot create fifo ‘/tmp/second’: File exists
mkfifo: cannot create fifo ‘/tmp/third’: File exists
然后,在第二个终端,我输入了:
$ echo "I was echoed to /tmp/first!" > /tmp/first
回到第一个仍在运行空闲线程的终端:
$ python fifo_issue.py
mkfifo: cannot create fifo ‘/tmp/first’: File exists
mkfifo: cannot create fifo ‘/tmp/second’: File exists
mkfifo: cannot create fifo ‘/tmp/third’: File exists
I was echoed to /tmp/first!
之后python正确退出