繁体   English   中英

记录多进程/多线程 python 脚本的死锁

[英]Deadlock with logging multiprocess/multithread python script

我面临从以下脚本收集日志的问题。 一旦我将SLEEP_TIME设置为太“小”的值,LoggingThread 线程就会以某种方式阻塞日志记录模块。 脚本冻结在action函数中的日志记录请求。 如果SLEEP_TIME大约为 0.1,则脚本会按我的预期收集所有日志消息。

我试图遵循这个答案,但它并没有解决我的问题。

import multiprocessing
import threading
import logging
import time

SLEEP_TIME = 0.000001

logger = logging.getLogger()

ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(): %(message)s'))
ch.setLevel(logging.DEBUG)

logger.setLevel(logging.DEBUG)
logger.addHandler(ch)


class LoggingThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            logger.debug('LoggingThread: {}'.format(self))
            time.sleep(SLEEP_TIME)


def action(i):
    logger.debug('action: {}'.format(i))


def do_parallel_job():

    processes = multiprocessing.cpu_count()
    pool = multiprocessing.Pool(processes=processes)
    for i in range(20):
        pool.apply_async(action, args=(i,))
    pool.close()
    pool.join()



if __name__ == '__main__':

    logger.debug('START')

    #
    # multithread part
    #
    for _ in range(10):
        lt = LoggingThread()
        lt.setDaemon(True)
        lt.start()

    #
    # multiprocess part
    #
    do_parallel_job()

    logger.debug('FINISH')

如何在多进程和多线程脚本中使用日志记录模块?

这可能是错误 6721

这个问题在任何有锁、线程和分叉的情况下都很常见。 如果线程 1 有锁,而线程 2 调用 fork,则在 fork 的进程中,将只有线程 2,并且锁将永远持有。 在你的情况下,那就是logging.StreamHandler.lock

可以在此处找到logging模块的修复程序( 永久链接)。 请注意,您还需要处理任何其他锁。

我最近在将日志模块与 Pathos 多处理库一起使用时遇到了类似的问题。 仍然不是 100% 确定,但似乎在我的情况下问题可能是由以下事实引起的,即日志处理程序试图重用来自不同进程的锁定对象。

能够使用围绕默认日志记录处理程序的简单包装器修复它:

import threading
from collections import defaultdict
from multiprocessing import current_process

import colorlog


class ProcessSafeHandler(colorlog.StreamHandler):
    def __init__(self):
        super().__init__()

        self._locks = defaultdict(lambda: threading.RLock())

    def acquire(self):
        current_process_id = current_process().pid
        self._locks[current_process_id].acquire()

    def release(self):
        current_process_id = current_process().pid
        self._locks[current_process_id].release()

默认情况下, multiprocessing处理在 Linux 上运行时会fork()池中的进程。 结果子进程将丢失除主线程之外的所有正在运行的线程。 因此,如果您使用的是 Linux,那就是问题所在。

第一个行动项目:你永远不应该使用基于 fork() 的池; 请参阅https://pythonspeed.com/articles/python-multiprocessing/https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods

在 Windows 上,我认为 macOS 上较新版本的 Python 使用了基于"spawn"的池。 这也是你应该在 Linux 上使用的。 在此设置中,将启动一个新的 Python 进程。 正如您所料,新进程没有来自父进程的任何线程,因为它是一个新进程。

第二个操作项:您需要在池中的每个子进程中完成日志记录设置; 父进程的日志设置不足以从工作进程获取日志。 您可以使用Poolinitializer关键字参数来执行此操作,例如编写一个名为setup_logging()的函数,然后执行pool = multiprocessing.Pool(initializer=setup_logging)https://docs.python.org/3/library/multiprocessing.html #module-multiprocessing.pool )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM