Python多处理:如何从子进程可靠地重定向stdout?

2024-05-12 23:40:50 发布

您现在位置:Python中文网/ 问答频道 /正文

注意。我见过Log output of multiprocessing.Process-不幸的是,它没有回答这个问题。

我正在通过多处理创建一个子进程(在windows上)。我希望将子进程的stdout和stderr输出的全部重定向到日志文件,而不是显示在控制台上。我看到的唯一建议是让子进程将sys.stdout设置为文件。但是,由于Windows上stdout重定向的行为,这并不能有效地重定向所有stdout输出。

为了说明这个问题,用以下代码构建一个Windows DLL

#include <iostream>

extern "C"
{
    __declspec(dllexport) void writeToStdOut()
    {
        std::cout << "Writing to STDOUT from test DLL" << std::endl;
    }
}

然后创建并运行如下python脚本,该脚本导入此DLL并调用函数:

from ctypes import *
import sys

print
print "Writing to STDOUT from python, before redirect"
print
sys.stdout = open("stdout_redirect_log.txt", "w")
print "Writing to STDOUT from python, after redirect"

testdll = CDLL("Release/stdout_test.dll")
testdll.writeToStdOut()

为了看到与我相同的行为,可能有必要针对不同于Python使用的C运行时构建DLL。在我的例子中,python是用Visual Studio 2010构建的,而我的DLL是用VS 2005构建的。

我看到的行为是控制台显示:

> stdout_test.py

Writing to STDOUT from python, before redirect

Writing to STDOUT from test DLL

而文件stdout_redirect_log.txt最终包含:

Writing to STDOUT from python, after redirect

换句话说,设置sys.stdout无法重定向由DLL生成的stdout输出。考虑到Windows中stdout重定向的底层api的性质,这并不奇怪。我以前在本机/C++级别遇到过这个问题,但从未找到一种方法来可靠地重定向STDUT。必须在外部完成。

这正是我启动子进程的原因——它使我可以从外部连接到它的管道,从而保证截取它的所有输出。我当然可以用pywin32手动启动进程,但我非常希望能够使用多处理的工具,特别是通过多处理管道对象与子进程通信的能力,以便获得进度更新。问题是,是否有任何方法可以同时对其IPC设施使用多处理来可靠地将子级的所有stdout和stderr输出重定向到一个文件。

更新:查看多处理的源代码。进程,它有一个静态成员,即Popen,看起来它可以用来重写用于创建进程的类。如果设置为None(默认),则使用多处理.forking。Popen,但它看起来像是

multiprocessing.Process._Popen = MyPopenClass

我可以重写进程创建。然而,尽管我可以从多处理.分叉.Popen中得到这个结果,但看起来我必须将一堆内部的东西复制到我的实现中,这听起来很不稳定,而且不太适合将来使用。如果这是唯一的选择的话,我想我可能会喜欢用pywin32手动完成整个过程。


Tags: 文件tofromtest进程windowsstdoutsys
3条回答

我想我偏离了底线,错过了一些东西,但值得一提的是,当我读到你的问题时,我想到了什么。

如果您可以截取所有的stdout和stderr(我从您的问题中得到了这种印象),那么为什么不在每个进程中添加或包装捕获功能呢?然后将通过队列捕获的内容发送给一个消费者,该消费者可以对所有输出执行您想要的任何操作?

我认为没有比将子流程重定向到您在评论中提到的文件更好的选择了。

在windows中,控制台stdin/out/err的工作方式是在每个进程诞生时定义其std handles。你可以用SetStdHandle来改变它们。当修改python的sys.stdout时,只修改python打印内容的位置,而不是其他DLL打印内容的位置。DLL中的CRT的一部分是使用GetStdHandle来找出要打印到的位置。如果需要,可以在DLL中的windows API或pywin32中的python脚本中进行任何管道操作。尽管我认为用subprocess会更简单。

您建议的解决方案很好:手动创建进程,这样您就可以显式地访问它们的stdout/stderr文件句柄。然后,您可以创建一个套接字来与子进程通信,并在该套接字上使用multiprocessing.connection(multiprocessing.Pipe创建相同类型的连接对象,因此这将为您提供所有相同的IPC功能)。

这里有一个两个文件的例子。

主.py:

import multiprocessing.connection
import subprocess
import socket
import sys, os

## Listen for connection from remote process (and find free port number)
port = 10000
while True:
    try:
        l = multiprocessing.connection.Listener(('localhost', int(port)), authkey="secret")
        break
    except socket.error as ex:
        if ex.errno != 98:
            raise
        port += 1  ## if errno==98, then port is not available.

proc = subprocess.Popen((sys.executable, "subproc.py", str(port)), stdout=subprocess.PIPE, stderr=subprocess.PIPE)

## open connection for remote process
conn = l.accept()
conn.send([1, "asd", None])
print(proc.stdout.readline())

子程序py:

import multiprocessing.connection
import subprocess
import sys, os, time

port = int(sys.argv[1])
conn = multiprocessing.connection.Client(('localhost', port), authkey="secret")

while True:
    try:
        obj = conn.recv()
        print("received: %s\n" % str(obj))
        sys.stdout.flush()
    except EOFError:  ## connection closed
        break

您可能还希望看到this question的第一个答案,以便从子进程获得非阻塞读取。

相关问题 更多 >