如何从使用多处理运行脚本的python2子进程获取输出?

时间:2017-12-04 03:59:03

标签: python python-2.7 subprocess multiprocessing

这是我的演示代码。它包含两个脚本。

第一个是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()。他们都没有工作。

所以,这是我的问题:

  1. 为什么subprocess无法通过多处理获取输出?为什么线程正常?

  2. 如果我注释掉stdout=subprocess.PIPEstderr=subprocess.PIPE,则输出会打印在我的控制台中。为什么?这是怎么发生的?

  3. 有没有机会从print_line.py获得输出?

2 个答案:

答案 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)

这不是问题,但 问题 - 或者更确切地说,它必须与标准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 openopen或以其他方式执行自己的操作来打开文件时,您可以通过其中一个可选参数指定缓冲。 sys.stdout的默认值是系统默认值,如果open则为行缓冲,否则为完全缓冲。奇怪的是,isatty()的默认值是行缓冲

当您向其添加换行符时,行缓冲流会自动应用codecs.open

无缓冲流立即将每个字节写入其输出。总的来说这是非常低效的。当缓冲区足够满时,完全缓冲的流写入其输出 - 这里“足够”的定义往往变化很大,从1024(1k)到1048576(1 MB) - 或明确指示时。

当您运行某个进程时,它是进程本身决定如何进行任何缓冲。您自己的Python代码,从进程读取,无法控制它。但是,如果你知道某些事情 - 或许多 - 关于你将要运行的进程,你可以设置他们的环境,使他们运行行缓冲,甚至无缓冲。 (或者,就像你的情况一样,因为你那段代码,你可以把它写成你想做的。)

1 有很多钩子可以很早地启动,你可以在这里大惊小怪。虽然工作起来很棘手。