Python 多进程与多线程混合情况下logging模块死锁问题

Python 多进程与多线程混合情况下logging模块死锁问题

死锁问题复现

在使用多线程与多进程混杂使用的情况下,有时启动多进程不成功,如下所示:

from multiprocessing import Pool
import threading
import logging
import os

logging.basicConfig(filename='./info.log', level=logging.INFO)
_logger = logging.getLogger(__name__)

def test_log():
    while 1:
        _logger.info('hello world')

def test_subprocess():
    _logger.info(f"{os.getpid()} start successful")


if __name__ == "__main__":
    threading.Thread(target=test_log, daemon=True).start()
    pool = Pool(3)
    for i in range(3):
        pool.apply_async(func=test_subprocess)
    while 1:
        pass

预期结果为:启动多个进程,然后日志输出 XXX start successful
实际结果:没有出现相应的结果,原因为logging并非进程安全的,所以导致死锁

原因详解

首先要了解logging是线程安全的,这是因为它自己本身有线程锁,每次在调用处理器的时候,首先获取锁,然后记录到日志里,最后解锁。

其次,需要了解multiprocess创建进程的原理是什么,具体的我也不是很清楚,但是从官方文档来看,multiprocess创建子进程是通过fork的形式,即从父进程复制相应的资源,创建子进程

最后思考为什么刚才的程序里为啥会死锁:

  1. 线程一直输出日志,logging的处理器一直占用锁,未释放
  2. 创建子进程的时候,子进程复制了父进程的资源,同时也复制了一个锁,注意从源码上来看:这个锁时threading.RLock()函数创建的,也就是必须由本身释放
  3. 此时,子进程的logging使用处理器的时候,首先获取锁,但是这个锁是由父进程创建的,子进程释放不了,所以子进程一直在获取,产生死锁

解决办法:绕过这个问题

了解到产生原因,那么就必然能够解决,当然有很多种解决办法,不过目前我的解决办法是:在子进程中,首先移除处理器,然后重新创建新的处理器。过程如下:

  1. 查看本身的记录器里是否有处理器,如果有,删除处理器
  2. 删除根处理器
  3. 重新创建根处理器
  4. 重新创建本身的处理器

删除根处理器的代码如下所示:

for handler in logging.root.handlers:
    logging.root.removeHandler(handler)

创建处理器的过程,和之前创建的过程一样,注意如果本身没有调用相关的逻辑,那么说明使用的是默认的根处理器,此时删除了,就必须重新创建处理器,使用logging.basicConfig即可

注意

使用字典的形式创建日志可能也会有问题,之前尝试过很多次,不成功,可能是logging.config.dictConfig函数本身会有bug,但是具体原因不清楚。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中,可以使用`multiprocessing`模块创建多个进程来并行执行任务。当多个进程同时执行时,由于每个进程都有自己的执行环境,因此它们的日志信息需要分别记录。为了实现多进程的日志记录,可以使用`multiprocessing`模块的`Queue`来处理日志信息的传递。 首先,我们可以在主进程中创建一个`Queue`对象,用于接收各个子进程的日志信息。然后,将这个`Queue`对象作为参数传递给每个子进程,在子进程中通过`logging`模块将日志信息发送到这个`Queue`中。主进程可以通过不断从`Queue`中获取日志信息并进行记录。 具体实现代码如下: ```python import logging import multiprocessing from multiprocessing import Queue # 配置logging logging.basicConfig(level=logging.INFO, format='%(asctime)s %(processName)s %(message)s') # 获取日志队列 def get_logger(queue): # 配置logger logger = logging.getLogger() logger.setLevel(logging.INFO) # 设置Handler handler = logging.QueueHandler(queue) logger.addHandler(handler) # 日志输出到控制台 console = logging.StreamHandler() console.setLevel(logging.INFO) logger.addHandler(console) logger.info('sub process started.') if __name__ == '__main__': # 创建日志队列 queue = Queue() # 创建子进程 p = multiprocessing.Process(target=get_logger, args=(queue,)) p.start() # 创建主进程的logger logger = logging.getLogger() logger.setLevel(logging.INFO) handler = logging.StreamHandler() handler.setLevel(logging.INFO) logger.addHandler(handler) # 主进程等待并获取日志信息进行记录 while True: try: record = queue.get(timeout=1) logger.handle(record) except Exception as e: break logger.info('all sub processes finished.') ``` 通过这种方式,可以实现多个子进程并行执行任务,并且可以记录每个子进程的日志信息,方便日志的统一管理和分析。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值