【问题标题】:Why overriding of sys.stdout does not work for output coming from multiprocessing.Process?为什么 sys.stdout 的覆盖不适用于来自 multiprocessing.Process 的输出?
【发布时间】:2018-10-20 22:57:59
【问题描述】:

我正在将标准输出重定向到记录器,现在我使用 multiprocessing.Process 生成了一个进程。然而,即使进程标准输出被重定向到父标准输出,它也会忽略 sys.stdout 覆盖。这是一个例子:

import multiprocessing
import sys
import logging

def worker():
    print('Hello from the multiprocessing')
    sys.stdout.flush()

class LoggerWriter:
    def __init__(self, logger, level):
        self.logger = logger
        self.level = level

    def write(self, message):
        if message != '\n':
            self.logger.log(self.level, "LOGGER: "+message)

    def flush(self):
        pass


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO, format='%(message)s')
    sys.stdout = LoggerWriter(logging.getLogger(), logging.INFO)
    p = multiprocessing.Process(target=worker)
    print("Hello from main")
    p.start()
    p.join()

我希望它会打印出来

LOGGER: Hello from main
LOGGER: Hello from the multiprocessing

但是我得到了

LOGGER: Hello from main
Hello from the multiprocessing

它完全忽略了 sys.stdout ...这是为什么呢?第一种情况能实现吗?

注意:这是在 Windows 7 上 - 似乎它可能会起作用。

【问题讨论】:

  • 您需要在每个进程中覆盖sys.stdout。检查this answer 作为在多处理设置中捕获 STDOUT/STDERR 的示例。
  • 因为你构造了一个new进程,也就是说它有自己的stdin、stdout等
  • 实际上,以上内容适用于我的 python3.7(尽管有一个额外的尾随空日志行)。这是 windows vs linux 的事情吗?
  • @AndrasDeak - 这是一个分叉与实际的新进程的东西。它可能发生在支持 fork 的系统上(并非所有多处理都可以通过 fork 实现),但在 Windows 上总是如此,因为它甚至不支持 fork。
  • @zwer 谢谢,会查的。

标签: python logging


【解决方案1】:

您使用的是 Windows,因此您正在使用 spawn 方法来启动多处理工作程序。此方法从头开始启动一个新的 Python 解释器,并在开始工作之前将您的脚本作为模块导入。

因为您的工作人员是从头开始而不是分叉的,所以他们不会自动继承主进程执行的设置,包括像您的标准输出包装器这样的设置,并且他们不会重做该设置,因为它在 if __name__ == '__main__' 守卫内.他们只有普通的sys.stdout

您必须安排工作人员设置他们自己的标准输出包装器,也许通过将包装器设置放在 if __name__ == '__main__' 保护之外。

【讨论】:

  • 谢谢,我想我已经实现了我所需要的,基本上每个工人都设置了他们的日志记录,而且我需要将日志记录到相同的资源,所以只创建了一个线程。当孩子通过队列进入时从孩子那里获取日志消息的线程(这有点讽刺,因为最初我想避免由于 GIL 限制等原因的线程,但是这样他们的功能至少限于处理这个日志记录问题,所以我猜它是好的)。
【解决方案2】:

根据 cmets 和 @user2357112 的回答,我最终使用 threading.Thread 处理来自子进程的日志(通过队列交换),同时仍使用 Process 进行实际工作。这是以防万一有人需要类似的东西。

基本上加完之后:

class LoggedProcess(multiprocessing.Process):

    class LoggerWriter:
        def __init__(self, queue):
            self.queue = queue

        def write(self, message):
            for line in message.rstrip().splitlines():
                self.queue.put(line.rstrip())

        def flush(self):
            pass

    @staticmethod
    def logged_worker(logger_queue, worker, *args, **kwargs):
        import sys
        sys.stdout = sys.stderr = LoggedProcess.LoggerWriter(logger_queue)
        logging.basicConfig(format="%(message)s", level=logging.INFO)
        try:
            worker(*args, **kwargs)
        except:
            pass
        logger_queue.put(None)

    @staticmethod
    def process_logger(process, logger_queue, name):
        while True:
            try:
                if not process.is_alive():
                    raise EOFError()
                msg = logger_queue.get(timeout=1)
                if msg is None:
                    raise EOFError()
                logging.getLogger().log(logging.INFO, f"[PROCESS {process.pid} {name}] {msg}")
            except queue.Empty:
                pass # timeout
            except Exception:
                break # queue closed

    def __init__(self, target, log_name='', args=(), kwargs={}):
        self.logger_queue = multiprocessing.Queue()
        self.log_name = log_name
        super().__init__(target=self.logged_worker, args=(self.logger_queue, target, *args), kwargs=kwargs)


    def start(self):
        super().start()
        logger_t = threading.Thread(target=self.process_logger, args=(self, self.logger_queue, self.log_name))
        logger_t.setDaemon(True)
        logger_t.start()

    def terminate(self):
        super().terminate()
        super().join()
        self.logger_queue.put(None)

我们可以将p = multiprocessing.Process(target=worker) 替换为p = LoggedProcess(target=worker)。现在子进程日志混合到父进程记录器中,在这种情况下导致

LOGGER: Hello from main
[PROCESS 5000 ] Hello from the multiprocessing

我已经添加了一些额外的进程信息,但最初的意图仍然可以满足,而且现在它可以被父进程全部放入同一个日志文件中,或者任何需要的东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-03
    • 2021-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-05
    • 2012-02-26
    • 2012-11-11
    相关资源
    最近更新 更多