资讯专栏INFORMATION COLUMN

threading + multiprocessing + logging = 死锁 ?

netmou / 761人阅读

摘要:后来通过调查发现是因为程序中同时使用了多线程,多进程以及模块,导致子进程中出现了死锁的情况。当创建子进程的时候,后台线程中的模块正好获取了一个锁在记录日志信息。

前段时间有个程序突然出现了子进程不工作的情况。

后来通过调查发现是因为程序中同时使用了多线程,多进程以及 logging 模块,导致子进程中出现了死锁的情况。

当创建子进程的时候,后台线程中的 logging 模块正好获取了一个锁(threading.RLock)在记录日志信息。由于在 unix/linux 平台下 Python 是通过 fork 来创建子进程的,因此创建子进程的时候会把 logging 中的锁也复制了一份,当子进程中需要记录日志的时候发现 logging 的锁一直处于被占用的状态,从而出现了死锁(复制的这个锁永远也不会被释放,因为它的所有者是父进程的某个线程,但是这个线程释放锁的时候又不会影响子进程里的这个锁)。

复现问题的代码如下:

import os
import sys
import threading
import time


class ThreadWorker(threading.Thread):
    def __init__(self):
        print("ThreadWorker: init")
        super().__init__()

    def run(self):
        print("ThreadWorker: running (rlock = {0})".format(global_rlock))

        global_rlock.acquire()
        print("ThreadWorker: i got lock {0}".format(global_rlock))
        time.sleep(5)
        global_rlock.release()
        print("ThreadWorker: release lock {0} and "
              "sleeping forever".format(global_rlock))

        time.sleep(600000)

global_rlock = threading.RLock(verbose=True)
worker = ThreadWorker()
worker.start()

time.sleep(1)
print("forking")
pid = os.fork()
if pid != 0:    # pid != 0 当前处于父进程
    print("parent: running (rlock = {0})".format(global_rlock))
else:      # pid = 0 当前处于子进程
    print("child: running (rlock = {0}), "
          "getting the lock...".format(global_rlock))
    global_rlock.acquire()
    print("child: got the lock {0}".format(global_rlock))
    sys.exit(0)

time.sleep(10)

上面代码的执行结果如下:

$ python fork.py
ThreadWorker: init
ThreadWorker: running (rlock = )
ThreadWorker: i got lock 
forking
parent: running (rlock = )
child: running (rlock = ), getting the lock...
ThreadWorker: release lock  and sleeping forever

从上面的结果中可以看出来:虽然线程随后释放了获得的锁,但是子进程却永远的卡在了获取锁的地方。

那么, 应该如何解决这个问题呢?至少有三种解决办法:

先创建子进程,然后再创建线程:

import os
import sys
import threading
import time


class ThreadWorker(threading.Thread):
    def __init__(self):
        print("ThreadWorker: init")
        super().__init__()

    def run(self):
        print("ThreadWorker: running (rlock = {0})".format(global_rlock))

        global_rlock.acquire()
        print("ThreadWorker: i got lock {0}".format(global_rlock))
        time.sleep(5)
        global_rlock.release()
        print("ThreadWorker: release lock {0} and "
              "sleeping forever".format(global_rlock))

        time.sleep(600000)

global_rlock = threading.RLock(verbose=True)
worker = ThreadWorker()

print("forking")
pid = os.fork()
if pid != 0:    # pid != 0 当前处于父进程
    print("parent: running (rlock = {0})".format(global_rlock))
    worker.start()
else:      # pid = 0 当前处于子进程
    time.sleep(1)
    print("child: running (rlock = {0}), "
          "getting the lock...".format(global_rlock))
    global_rlock.acquire()
    print("child: got the lock {0}".format(global_rlock))
    global_rlock.release()
    print("child: release the lock {0}".format(global_rlock))
    sys.exit(0)

time.sleep(10)

结果:

$ python fork2.py
ThreadWorker: init
forking
parent: running (rlock = )
ThreadWorker: running (rlock = )
ThreadWorker: i got lock 
child: running (rlock = ), getting the lock...
child: got the lock 
child: release the lock 
ThreadWorker: release lock  and sleeping forever

可以看到子进程和线程都能够正常获取锁。

不要混合使用 threading, multiprocessing, logging/其他使用了线程锁的模块。 要么都是多线程,要么都是多进程。

另一个办法就是配置 logging 使用无锁的 handler 来记录日志信息。

参考资料

PythonLoggingThreadingMultiprocessingIntermixedStudy(Using modules Python logging, threading and multiprocessing in a single application.)

Issue 6721: Locks in the standard library should be sanitized on fork - Python tracker

multithreading - Deadlock with logging multiprocess/multithread python script - Stack Overflow

python - 使用multiprocessing.Process调用start方法后,有较小的几率子进程中run方法未执行 - SegmentFault

python multiprocessing hanging, potential queue memory error? - Stack Overflow

Threads and fork(): think twice before mixing them. | Linux Programming Blog

原文地址: https://mozillazg.com/2016/09...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44210.html

相关文章

  • 「Python 面试」第三次更新

    摘要:说一下进程线程以及多任务多进程多线程和协程进程概念一个程序对应一个进程,这个进程被叫做主进程,而一个主进程下面还有许多子进程。避免了由于系统在处理多进程或者多线程时,切换任务时需要的等待时间。 showImg(https://segmentfault.com/img/bVbuYxg?w=3484&h=2480); 阅读本文大约需要 10 分钟。 14.说一下进程、线程、以及多任务(多进...

    wslongchen 评论0 收藏0
  • Python_系统编程

    摘要:主进程会等待所有的子进程先结束,然后再结束主进程。关闭进程池,关闭后实例不再接收新的请求等待实例中的所有子进程执行完毕,主进程才会退出,必须放在语句之后。主进程一般都用来等待,任务在子进程中执行。 多任务:同一个时间段中,执行多个函数/运行多个程序. 操作系统可以同时运行多个任务:操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,...

    wuaiqiu 评论0 收藏0
  • Python多线程

    摘要:多线程的理解多进程和多线程都可以执行多个任务,线程是进程的一部分。多线程创建在中,同样可以实现多线程,有两个标准模块和,不过我们主要使用更高级的模块。多线程的应用场景。 1、多线程的理解 多进程和多线程都可以执行多个任务,线程是进程的一部分。线程的特点是线程之间可以共享内存和变量,资源消耗少(不过在Unix环境中,多进程和多线程资源调度消耗差距不明显,Unix调度较快),缺点是线程之间...

    dcr309duan 评论0 收藏0
  • python---线程

    摘要:某进程内的线程在其它进程不可见。线程的实体包括程序数据和。包括以下信息线程状态。当线程不运行时,被保存的现场资源。用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。线程能够利用的表空 操作系统线程理论 线程概念的引入背景 进程之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运...

    Yangyang 评论0 收藏0
  • PyTips 0x 12 - Python 线程与协程(1)

    摘要:中关于线程的标准库是,之前在版本中的在之后更名为,无论是还是都应该尽量避免使用较为底层的而应该使用。而与线程相比,协程尤其是结合事件循环无论在编程模型还是语法上,看起来都是非常友好的单线程同步过程。 项目地址:https://git.io/pytips 要说到线程(Thread)与协程(Coroutine)似乎总是需要从并行(Parallelism)与并发(Concurrency)谈起...

    el09xccxy 评论0 收藏0

发表评论

0条评论

netmou

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<