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

Tom*_*Tom 28 python windows stdout multiprocessing stderr

NB.我已经看到了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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后创建并运行如下所示的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()
Run Code Online (Sandbox Code Playgroud)

为了看到与我相同的行为,可能需要针对与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
Run Code Online (Sandbox Code Playgroud)

文件stdout_redirect_log.txt最终包含:

Writing to STDOUT from python, after redirect
Run Code Online (Sandbox Code Playgroud)

换句话说,设置sys.stdout无法重定向DLL生成的stdout输出.鉴于Windows中stdout重定向的基础API的性质,这并不令人惊讶.我以前在本机/ C++级别遇到过这个问题,但从未找到一种方法可以在进程内可靠地重定向stdout.它必须在外部完成.

这实际上是我启动子进程的原因 - 这样我就可以从外部连接到它的管道,从而保证我可以拦截它的所有输出.我可以通过使用pywin32手动启动进程来实现这一点,但我非常希望能够使用多处理功能,特别是通过多处理Pipe对象与子进程通信的能力,以便获得进步更新.问题是,是否有任何方法既可以为其IPC工具使用多处理,可以将所有子项的stdout和stderr输出可靠地重定向到文件.

更新:查看multiprocessing.Processs的源代码,它有一个静态成员_Popen,看起来它可以用来覆盖用于创建进程的类.如果它设置为None(默认值),它使用multiprocessing.forking._Popen,但它看起来像是说

multiprocessing.Process._Popen = MyPopenClass
Run Code Online (Sandbox Code Playgroud)

我可以覆盖流程创建.然而,虽然我可以从multiprocessing.forking._Popen中得到这个,但看起来我必须将一堆内部东西复制到我的实现中,这听起来很脆弱,而且不太适合未来.如果这是唯一的选择我认为我可能会用pywin32手动完成整个事情.

Luk*_*uke 9

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

这是一个双文件示例.

master.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())
Run Code Online (Sandbox Code Playgroud)

subproc.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
Run Code Online (Sandbox Code Playgroud)

您可能还希望看到此问题的第一个答案,以从子进程获取非阻塞读取.