这是我的演示代码。它包含两个脚本。
第一个是main.py
,它将使用子流程模块调用print_line.py
。
第二个是print_line.py
,它会向stdout打印一些内容。
main.py
import subprocess
p = subprocess.Popen('python2 print_line.py',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
shell=True,
universal_newlines=True)
while True:
line = p.stdout.readline()
if line:
print(line)
else:
break
print_line.py
from multiprocessing import Process, JoinableQueue, current_process
if __name__ == '__main__':
task_q = JoinableQueue()
def do_task():
while True:
task = task_q.get()
pid = current_process().pid
print 'pid: {}, task: {}'.format(pid, task)
task_q.task_done()
for _ in range(10):
p = Process(target=do_task)
p.daemon = True
p.start()
for i in range(100):
task_q.put(i)
task_q.join()
之前,print_line.py
是用线程和队列模块编写的,一切都很好。但现在,在更改为多处理模块后,main.py
无法从print_line获得任何输出。我尝试使用Popen.communicate()
获取输出或在preexec_fn=os.setsid
中设置Popen()
。他们都没有工作。
所以,这是我的问题:
为什么subprocess无法通过多处理获取输出?为什么线程正常?
如果我注释掉stdout=subprocess.PIPE
和stderr=subprocess.PIPE
,则输出会打印在我的控制台中。为什么?这是怎么发生的?
有没有机会从print_line.py
获得输出?
答案 0 :(得分:2)
好奇。
理论上,这个应该按原样工作,但事实并非如此。原因是在缓冲IO的深层,阴暗的水域中的某个地方。如果没有刷新,子进程的子进程的输出似乎会丢失。
您有两种解决方法:
一种是在print_line.py中使用flush()
:
def do_task():
while True:
task = task_q.get()
pid = current_process().pid
print 'pid: {}, task: {}'.format(pid, task)
sys.stdout.flush()
task_q.task_done()
这将解决问题,因为一旦你写了东西就会刷新你的标准输出。
另一个选择是在main.py中使用-u
标志:
p = subprocess.Popen('python2 -u print_line.py',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
shell=True,
universal_newlines=True)
-u
将强制stdin和stdout在print_line.py
中完全无缓冲,print_line.py
的子项将继承此行为。
这些是解决问题的方法。如果你对理论感兴趣,为什么会发生这种情况,如果子进程终止,它肯定与未刷新的stdout有关,但我不是这方面的专家。
答案 1 :(得分:1)
这不是multiprocessing问题,但 是subprocess问题 - 或者更确切地说,它必须与标准I / O和缓冲一样,如{ {3}}。诀窍是,默认情况下,任何进程的输出,无论是否在Python中,如果输出设备是由“{确定的”“终端设备”,则行缓冲 {1}}:
os.isatty(stream.fileno())
有一个Hannu's answer:
>>> import sys
>>> sys.stdout.fileno()
1
>>> import os
>>> os.isatty(1)
True
但>>> sys.stdout.isatty()
True
操作更为基础。也就是说,在内部,Python首先使用os.isatty()
检查文件描述符,然后根据结果(和/或参数和/或用于打开流的函数)选择流的缓冲。在你通常有很多控制权之前,os.isatty(fd)
流在Python启动期间就会被打开。 1
当您致电shortcut available to you once the stream is open或open
或以其他方式执行自己的操作来打开文件时,您可以通过其中一个可选参数指定缓冲。 sys.stdout
的默认值是系统默认值,如果open
则为行缓冲,否则为完全缓冲。奇怪的是,isatty()
的默认值是行缓冲。
当您向其添加换行符时,行缓冲流会自动应用codecs.open
。
无缓冲流立即将每个字节写入其输出。总的来说这是非常低效的。当缓冲区足够满时,完全缓冲的流写入其输出 - 这里“足够”的定义往往变化很大,从1024(1k)到1048576(1 MB) - 或明确指示时。
当您运行某个进程时,它是进程本身决定如何进行任何缓冲。您自己的Python代码,从进程读取,无法控制它。但是,如果你知道某些事情 - 或许多 - 关于你将要运行的进程,你可以设置他们的环境,使他们运行行缓冲,甚至无缓冲。 (或者,就像你的情况一样,因为你写那段代码,你可以把它写成你想做的。)
1 有很多钩子可以很早地启动,你可以在这里大惊小怪。虽然工作起来很棘手。