Python多线程死锁原理与RLock解决方案详解
本文深入解析Python多线程环境下的死锁问题,通过银行家算法类比阐述资源分配原理。重点演示了传统互斥锁导致死锁的代码场景,并详细讲解如何利用RLock递归锁解决同一线程多次请求资源的问题,附带完整代码示例与输出分析。
在Python多线程编程中,解决死锁问题主要有两种思路:一是为锁操作添加超时时间,避免无限等待;二是使用支持重入的锁机制。
fromthreading import Thread, Lock
importtime
mutex1= Lock() # 创建一个互斥锁
mutex2= Lock() # 创建一个互斥锁
def fun1():
while True:
mutex1.acquire()
print("线程1 锁住了mutex1")
time.sleep(0.1)
result =mutex2.acquire(timeout=1) # timeout指明acquire等的最长超时时间
# result = mutex2.acquire(False) # 非阻塞
if result:
# 表示对mutex2成功上锁
print("线程1 锁住了mutex2")
print("线程1 hello")
mutex1.release()
mutex2.release()
break
else:
# 表示对mutex2上锁失败
mutex1.release() # 将mutex1释放,保证别人能够执行
time.sleep(0.1)
def fun2():
mutex2.acquire()
print("线程2 锁住了mutex2")
time.sleep(0.1)
mutex1.acquire()
print("线程2 锁住了mutex1")
print("线程2 hi")
mutex1.release()
mutex2.release()
附录部分将介绍银行家算法,该算法虽非Python标准库内容,但有助于理解操作系统中资源分配与死锁避免的核心逻辑,建议读者理解其思想即可。
背景知识
银行家算法源于一个经典的资源分配场景:银行家需将资金安全地借给多个客户,确保每位客户都能完成业务,同时银行家能收回全部资金而不破产。在操作系统中,这对应于进程资源分配问题:银行家相当于操作系统,客户相当于运行中的进程,资金则对应系统资源。
问题描述
假设银行家持有一定数量的资金,有多个客户申请贷款。每个客户需在开始时声明所需贷款总额。若客户申请的总额不超过银行家的资金总数,银行家可接受申请。贷款以单位形式(如1万元)逐步发放,客户在借满前可能需等待,但银行家必须保证这种等待是有限的且最终可完成。
例如,有三个客户C1、C2、C3向银行家借款,银行家总资金为10个单位。其中C1需9个单位,C2需3个单位,C3需8个单位,总计20个单位。某一时刻的状态如图所示。

针对图a的状态,根据安全序列要求,首个被满足的客户所需贷款必须小于等于银行家当前剩余资金。图中仅C2满足条件(C2需1个单位,银行家剩余2个单位)。银行家将1个单位借给C2,C2完成工作后归还3个单位,状态转为图b。同理,银行家借出4个单位给C3,C3完成工作后状态转为图c。此时仅剩C1,其需7个单位,而银行家手中已有8个单位,故C1也能顺利借款并完成工作。最终(见图d),银行家收回全部10个单位,确保不亏损。因此,序列{C1, C2, C3}是一个安全序列。若按此序列贷款,银行家处于安全状态。反之,若在图b状态时银行家将4个单位借给C1,则进入不安全状态:此时C1和C3均无法完成工作,且银行家手中无资金,系统陷入僵持,银行家也无法收回投资。
综上所述,银行家算法从当前状态出发,按安全序列逐一检查各客户能否完成工作。假定某客户完成工作并归还全部贷款后,再检查下一个能完成工作的客户。若所有客户均能完成工作,则找到安全序列,银行家处于安全状态。
补充:Python基础-死锁与递归锁
死锁
死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成互相等待的现象。若无外力作用,这些进程或线程将无法继续推进,此时称系统处于死锁状态,这些永远在互相等待的进程称为死锁进程。
from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('\033[41m%s 拿到A锁\033[0m' %self.name)
mutexB.acquire()
print('\033[42m%s 拿到B锁\033[0m' %self.name)
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('\033[43m%s 拿到B锁\033[0m' %self.name)
time.sleep(2)
mutexA.acquire()
print('\033[44m%s 拿到A锁\033[0m' %self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(5):
t=MyThread()
t.start()
输出如下:
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
分析上述代码如何产生死锁:
启动5个线程执行run方法。假设thread1首先抢到A锁,此时未释放A锁,接着执行代码mutexB.acquire()并成功抢到B锁。在抢B锁时,因A锁未释放,其他线程只能等待。thread1执行完func1后继续执行func2。与此同时,在func2中执行mutexB.acquire()并抢到B锁,随后进入睡眠状态。当thread1执行完func1释放A、B锁时,其他剩余线程开始抢A锁执行func1。若thread2抢到A锁,接下来需抢B锁。在此期间,thread1已执行func2并抢到B锁,随后sleep(2)持有B锁未释放。由于无其他线程争抢,thread1只能睡眠。此时thread1持有B锁,而thread2需抢B锁,从而形成死锁。
递归锁
我们分析了死锁,那么Python中是如何解决这样的递归锁呢?
在Python中,为了支持同一线程多次请求同一资源,Python提供了可重入锁RLock。
RLock内部维护着一个Lock和一个counter变量,counter记录acquire的次数,从而使资源可被多次require。直到一个线程所有的acquire都被release,其他线程才能获得资源。上述例子若使用RLock代替Lock,则不会发生死锁:
from threading import Thread,Lock,RLock
import time
mutexA=mutexB=RLock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutexA.acquire()
print('%s 拿到A锁' %self.name)
mutexB.acquire()
print('%s 拿到B锁' %self.name)
mutexB.release()
mutexA.release()
def f2(self):
mutexB.acquire()
print('%s 拿到B锁' % self.name)
time.sleep(0.1)
mutexA.acquire()
print('%s 拿到A锁' % self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(5):
t=MyThread()
t.start()
输出代码如下:
E:\python\python_sdk\python.exe "E:/python/py_pro/3 死锁现象与递归锁.py"
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-1 拿到A锁
Thread-2 拿到A锁
Thread-2 拿到B锁
Thread-2 拿到B锁
Thread-2 拿到A锁
Thread-4 拿到A锁
Thread-4 拿到B锁
Thread-4 拿到B锁
Thread-4 拿到A锁
Thread-3 拿到A锁
Thread-3 拿到B锁
Thread-3 拿到B锁
Thread-3 拿到A锁
Thread-5 拿到A锁
Thread-5 拿到B锁
Thread-5 拿到B锁
Thread-5 拿到A锁
Process finished with exit code 0
或者如下的效果:

来解释下递归锁的代码:
由于锁A和B是同一个递归锁,thread1拿到A、B锁后,counter记录了acquire的次数为2次。在func1执行完毕后,释放递归锁。thread1释放完递归锁并执行完func1代码后,接下来有两种可能:1、thread1再次抢到递归锁,执行func2代码;2、其他线程抢到递归锁,去执行func1的任务代码。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。如有错误或未考虑完全的地方,望不吝赐教。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









