一、前言
- 线程互斥锁分为递归锁和非递归锁。
- 互斥锁默认是非递归锁
- 如果一个线程多次获取非递归锁,就会出现死锁现象
- 如果一个线程可以多次获取同一个递归锁,不会产生死锁。
二、死锁现象
2.1什么是死锁
指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
2.2 死锁实例
from threading import Thread,RLock,Lock
import time
# 死锁现象
# ==================================================================
mutexA = Lock() # 锁A
mutexB = Lock() # 锁B
class Task(Thread):
def run(self):
self.F1()
self.F2()
def F1(self):
mutexA.acquire() # 加A锁
print('%s 拿到了A锁' % self.name)
mutexB.acquire() # 加B锁
print('%s 拿到了B锁' % self.name)
mutexB.release() # 释放B锁
mutexA.release() # 释放A锁
def F2(self):
mutexB.acquire() # 加B锁
print('%s 拿到了B锁' % self.name)
time.sleep(0.1) # IO
mutexA.acquire() # 加A锁
print('%s 拿到了A锁' % self.name)
mutexA.release() # 释放A锁
mutexB.release() # 释放B锁
if __name__ == '__main__':
obj = Task()
for i in range(10):
obj = Task()
obj.start()
结果:
Thread-2 拿到了A锁
Thread-2 拿到了B锁
Thread-2 拿到了B锁
Thread-3 拿到了A锁
# 程序卡死
2.3死锁实例分析
先运行的(假定 Thread-1)线程在F2函数体IO的时候拿到B锁的同时,(假定 Thread-2)在F1函数体内拿到了A锁
Thread-1想继续运行,必须得拿到A锁然后才能释放B锁,Thread-2想继续运行必须拿到B锁然后才能释放A锁,两个线程互不相让,这个时候就导致了阻塞。
三、递归锁
解决死锁问题就要用到递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
3.1 RLock和Lock的区别
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
二者的区别是:递归锁(RLock)可以连续acquire多次,而互斥锁(Lock)只能acquire一次
3.2 递归锁实例
# 怎么解决死锁?
# 运用递归锁 RLock()
# ==================================================================
from threading import Thread,RLock
import time
mutexA = mutexB = RLock() # 这相当于1把锁,mutexA\ mutexB 只是指向同一个内存地址
class Task(Thread):
def run(self):
self.F1()
self.F2()
def F1(self):
mutexA.acquire() # 加A锁
print('%s 拿到了A锁' % self.name)
mutexB.acquire() # 加B锁
print('%s 拿到了B锁' % self.name)
mutexB.release() # 释放B锁
mutexA.release() # 释放A锁
def F2(self):
mutexB.acquire() # 加B锁
print('%s 拿到了B锁' % self.name)
time.sleep(1) # IO
mutexA.acquire() # 加A锁
print('%s 拿到了A锁' % self.name)
mutexA.release() # 释放A锁
mutexB.release() # 释放B锁
if __name__ == '__main__':
obj = Task()
for i in range(10):
obj = Task()
obj.start()
结果:
Thread-2 拿到了A锁
Thread-2 拿到了B锁
Thread-2 拿到了B锁
Thread-2 拿到了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锁
Thread-7 拿到了A锁
Thread-7 拿到了B锁
Thread-7 拿到了B锁
Thread-7 拿到了A锁
Thread-9 拿到了A锁
Thread-9 拿到了B锁
Thread-9 拿到了B锁
Thread-9 拿到了A锁
Thread-11 拿到了A锁
Thread-11 拿到了B锁
Thread-11 拿到了B锁
Thread-11 拿到了A锁
Thread-6 拿到了A锁
Thread-6 拿到了B锁
Thread-6 拿到了B锁
Thread-6 拿到了A锁
Thread-10 拿到了A锁
Thread-10 拿到了B锁
Thread-10 拿到了B锁
Thread-10 拿到了A锁
Thread-8 拿到了A锁
Thread-8 拿到了B锁
Thread-8 拿到了B锁
Thread-8 拿到了A锁
Thread-4 拿到了A锁
Thread-4 拿到了B锁
Thread-4 拿到了B锁
Thread-4 拿到了A锁
3.3 递归锁实例分析
由于锁A,B是同一个递归锁,Thread-2拿到A,B锁,counter记录了acquire的次数2次,然后在F1函数体代码执行完毕,就释放递归锁,在thread-2释放完递归锁,执行完F1代码,接下来会有2种可能,1、Thread-2再次抢到递归锁,执行F2函数体代码 2、其他的线程抢到递归锁,去执行F1函数体代码
四、互斥锁总结
- 死锁现象从本质上来讲是一种逻辑错
- 递归锁没有从根本上解决死锁问题,它只是牺牲了时间和空间的
- 不提倡使用递归锁,用到递归锁的时候,说明程序有逻辑上的错误
- 在程序逻辑有问题时,产生死锁暴露出问题。有些逻辑特别混乱或涉及三方开发对外提供服务不得已时,再用递归锁