NB。我见过Log output of multiprocessing.Process - 不幸的是,它没有回答这个问题。
我正在通过多处理创建子进程(在Windows上)。我希望将子进程的stdout和stderr输出的 all 重定向到日志文件,而不是出现在控制台上。我看到的唯一建议是子进程将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 ++级别遇到过这个问题,但从未找到一种方法可以在进程内可靠地重定向stdout。它必须在外部完成。
这实际上是我启动子进程的原因 - 这样我可以在外部连接到它的管道,从而保证我可以拦截它的所有输出。我可以通过使用pywin32手动启动进程来实现这一点,但我非常希望能够使用多处理功能,特别是通过多处理Pipe对象与子进程通信的能力,以便获得进步更新。问题是,是否有任何方法可以为其IPC工具和使用多处理,以便可靠地将所有子项的stdout和stderr输出重定向到文件。
UPDATE:查看multiprocessing.Processs的源代码,它有一个静态成员_Popen,看起来它可以用来覆盖用于创建进程的类。如果它设置为None(默认值),它使用multiprocessing.forking._Popen,但它看起来像是说
multiprocessing.Process._Popen = MyPopenClass
我可以覆盖流程创建。然而,虽然我可以从multiprocessing.forking._Popen中得到这个,但看起来我必须将一堆内部东西复制到我的实现中,这听起来很脆弱,而且不太适合未来。如果这是唯一的选择我认为我可能会用pywin32手动完成整个事情。
答案 0 :(得分:7)
您建议的解决方案是一个很好的解决方案:手动创建您的流程,以便您可以显式访问其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())
<强> 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
您可能还希望看到this question的第一个答案,以便从子流程中获取非阻塞读取。
答案 1 :(得分:1)
我认为您没有比在评论中提到的将子流程重定向到文件更好的选择。
控制台stdin / out / err在Windows中的工作方式是它出生时的每个进程都定义了std handles。您可以使用SetStdHandle更改它们。当你修改python的sys.stdout
时,你只能修改python打印出来的东西,而不是其他DLL打印的东西。 DLL中的部分CRT使用GetStdHandle来查找要打印到的位置。如果你愿意,你可以在你的DLL或你的python 32脚本中使用pywin32做你想要的任何管道。虽然我确实认为subprocess会更简单。
答案 2 :(得分:0)
我认为我已经离开了基地,并且遗漏了一些东西,但是在我看到你的问题时,我想到的是什么。
如果您可以拦截所有stdout和stderr(我从您的问题中得到了这种印象),那么为什么不在每个进程周围添加或包装捕获功能呢?然后将通过队列捕获的内容发送给消费者,消费者可以使用所有输出执行任何操作吗?
答案 3 :(得分:0)
在我的情况下,我更改了sys.stdout.write
以写到PySide QTextEdit。我无法读取sys.stdout
,也不知道如何更改sys.stdout
以使其可读。我创建了两个管道。一个用于stdout,另一个用于stderr。在单独的过程中,我将sys.stdout
和sys.stderr
重定向到多处理管道的子连接。在主进程上,我创建了两个线程来读取stdout和stderr父管道,并将管道数据重定向到sys.stdout
和sys.stderr
。
import sys
import contextlib
import threading
import multiprocessing as mp
import multiprocessing.queues
from queue import Empty
import time
class PipeProcess(mp.Process):
"""Process to pipe the output of the sub process and redirect it to this sys.stdout and sys.stderr.
Note:
The use_queue = True argument will pass data between processes using Queues instead of Pipes. Queues will
give you the full output and read all of the data from the Queue. A pipe is more efficient, but may not
redirect all of the output back to the main process.
"""
def __init__(self, group=None, target=None, name=None, args=tuple(), kwargs={}, *_, daemon=None,
use_pipe=None, use_queue=None):
self.read_out_th = None
self.read_err_th = None
self.pipe_target = target
self.pipe_alive = mp.Event()
if use_pipe or (use_pipe is None and not use_queue): # Default
self.parent_stdout, self.child_stdout = mp.Pipe(False)
self.parent_stderr, self.child_stderr = mp.Pipe(False)
else:
self.parent_stdout = self.child_stdout = mp.Queue()
self.parent_stderr = self.child_stderr = mp.Queue()
args = (self.child_stdout, self.child_stderr, target) + tuple(args)
target = self.run_pipe_out_target
super(PipeProcess, self).__init__(group=group, target=target, name=name, args=args, kwargs=kwargs,
daemon=daemon)
def start(self):
"""Start the multiprocess and reading thread."""
self.pipe_alive.set()
super(PipeProcess, self).start()
self.read_out_th = threading.Thread(target=self.read_pipe_out,
args=(self.pipe_alive, self.parent_stdout, sys.stdout))
self.read_err_th = threading.Thread(target=self.read_pipe_out,
args=(self.pipe_alive, self.parent_stderr, sys.stderr))
self.read_out_th.daemon = True
self.read_err_th.daemon = True
self.read_out_th.start()
self.read_err_th.start()
@classmethod
def run_pipe_out_target(cls, pipe_stdout, pipe_stderr, pipe_target, *args, **kwargs):
"""The real multiprocessing target to redirect stdout and stderr to a pipe or queue."""
sys.stdout.write = cls.redirect_write(pipe_stdout) # , sys.__stdout__) # Is redirected in main process
sys.stderr.write = cls.redirect_write(pipe_stderr) # , sys.__stderr__) # Is redirected in main process
pipe_target(*args, **kwargs)
@staticmethod
def redirect_write(child, out=None):
"""Create a function to write out a pipe and write out an additional out."""
if isinstance(child, mp.queues.Queue):
send = child.put
else:
send = child.send_bytes # No need to pickle with child_conn.send(data)
def write(data, *args):
try:
if isinstance(data, str):
data = data.encode('utf-8')
send(data)
if out is not None:
out.write(data)
except:
pass
return write
@classmethod
def read_pipe_out(cls, pipe_alive, pipe_out, out):
if isinstance(pipe_out, mp.queues.Queue):
# Queue has better functionality to get all of the data
def recv():
return pipe_out.get(timeout=0.5)
def is_alive():
return pipe_alive.is_set() or pipe_out.qsize() > 0
else:
# Pipe is more efficient
recv = pipe_out.recv_bytes # No need to unpickle with data = pipe_out.recv()
is_alive = pipe_alive.is_set
# Loop through reading and redirecting data
while is_alive():
try:
data = recv()
if isinstance(data, bytes):
data = data.decode('utf-8')
out.write(data)
except EOFError:
break
except Empty:
pass
except:
pass
def join(self, *args):
# Wait for process to finish (unless a timeout was given)
super(PipeProcess, self).join(*args)
# Trigger to stop the threads
self.pipe_alive.clear()
# Pipe must close to prevent blocking and waiting on recv forever
if not isinstance(self.parent_stdout, mp.queues.Queue):
with contextlib.suppress():
self.parent_stdout.close()
with contextlib.suppress():
self.parent_stderr.close()
# Close the pipes and threads
with contextlib.suppress():
self.read_out_th.join()
with contextlib.suppress():
self.read_err_th.join()
def run_long_print():
for i in range(1000):
print(i)
print(i, file=sys.stderr)
print('finished')
if __name__ == '__main__':
# Example test write (My case was a QTextEdit)
out = open('stdout.log', 'w')
err = open('stderr.log', 'w')
# Overwrite the write function and not the actual stdout object to prove this works
sys.stdout.write = out.write
sys.stderr.write = err.write
# Create a process that uses pipes to read multiprocess output back into sys.stdout.write
proc = PipeProcess(target=run_long_print, use_queue=True) # If use_pipe=True Pipe may not write out all values
# proc.daemon = True # If daemon and use_queue Not all output may be redirected to stdout
proc.start()
# time.sleep(5) # Not needed unless use_pipe or daemon and all of stdout/stderr is desired
# Close the process
proc.join() # For some odd reason this blocks forever when use_queue=False
# Close the output files for this test
out.close()
err.close()
答案 4 :(得分:0)
这是捕获multiprocessing.Process的标准输出的简单明了的方法:
import app
import io
import sys
from multiprocessing import Process
def run_app(some_param):
sys.stdout = io.TextIOWrapper(open(sys.stdout.fileno(), 'wb', 0), write_through=True)
app.run()
app_process = Process(target=run_app, args=('some_param',))
app_process.start()
# Use app_process.termninate() for python <= 3.7.
app_process.kill()