多处理:我怎样才能 ʀᴇʟɪᴀʙʟʏ 从子进程重定向标准输出?

新手上路,请多包涵

注意。我看过 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 生成的标准输出输出。考虑到 Windows 中标准输出重定向的底层 API 的性质,这并不奇怪。我以前在本机/C++ 级别遇到过这个问题,但从未找到一种从进程内可靠地重定向 stdout 的方法。它必须在外部完成。

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

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

multiprocessing.Process._Popen = MyPopenClass

我可以覆盖流程创建。然而,虽然我可以从 multiprocessing.forking._Popen 中派生出它,但看起来我必须将一堆内部内容复制到我的实现中,这听起来很不稳定而且不太适合未来。如果那是唯一的选择,我想我可能会选择使用 pywin32 手动完成所有操作。

原文由 Tom 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 854
2 个回答

您建议的解决方案是一个很好的解决方案:手动创建您的进程,以便您可以明确访问它们的 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

您可能还想查看 此问题 的第一个答案,以从子进程中获取非阻塞读取。

原文由 Luke 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

控制台 stdin/out/err 在 Windows 中的工作方式是每个进程在它诞生时都定义了它的 std 句柄。您可以使用 SetStdHandle 更改它们。当你修改 python 的 sys.stdout 你只修改 python 打印东西的地方,而不是其他 DLL 打印东西的地方。 DLL 中的部分 CRT 正在使用 GetStdHandle 找出要打印到的位置。如果你愿意,你可以在你的 DLL 或你的 python 脚本中使用 pywin32 在 Windows API 中做任何你想做的管道。尽管我确实认为使用 subprocess 会更简单。

原文由 ubershmekel 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题