「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。
在上一篇文章 《【Python】线程间同步的实现与代码》中,已经介绍了线程间同步常用的方法,及如何在 Python 中使用互斥锁 Lock,在 threading 中,除了 Lock,还有另一种锁 RLock 。
可重入锁
广义上的可重入锁指的是可重复可递归调用的锁。
也就是在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
Lock 与 RLock
threading 中,有 Lock 和 RLock 两种锁。
Lock 被称为原始锁
- 有两个基本方法,
acquire()和release()- 当状态为非锁定时,
acquire()将状态改为锁定并立即返回 - 当状态是锁定时,
acquire()将阻塞至其他线程调用release()将其改为非锁定状态 release()只在锁定状态下调用,将状态改为非锁定并立即返回- 如果尝试释放一个非锁定的锁,则会引发
RuntimeError异常
- 当状态为非锁定时,
- 在锁定时不属于特定线程,可以在一个线程中上锁,在另一个线程中解锁
- 支持上下文管理协议,即支持
with语句
RLock 被称为重入锁
- 有两个基本方法,
acquire()和release()- 若要锁定锁,线程调用其
acquire()方法,一旦线程拥有了锁,方法将返回 - 若要解锁,线程调用
release()方法 - 线程必须在每次获取锁时释放一次
- 若要锁定锁,线程调用其
acquire()/release()对可以嵌套,重入锁必须由获取它的线程释放- 一旦线程获得了重入锁,同一个线程再次获取它将不阻塞
- 支持上下文管理协议,即支持
with语句
重入原始锁
threading 中的 Lock 是原始锁,线程在获取到锁后,如果试图再次获取,则会被阻塞。
举个栗子:
import threading
mutex = threading.Lock()
def do_something():
if mutex.acquire(): # 获取锁
print(threading.currentThread().name + " get lock")
if mutex.acquire(): # 再次获取锁
print(threading.currentThread().name + " get lock against")
mutex.release()
mutex.release()
t1 = threading.Thread(target= do_something, name= "t1")
t2 = threading.Thread(target= do_something, name= "t2")
t1.start()
t2.start()
t1.join()
t2.join()
代码输出为: t1 get lock
上述代码中,线程 t1 获取锁后,试图再次获取锁,此时锁无法被再次获取,线程被阻塞。由于线程 t1 获取了锁,线程 t2 无法获取锁,处于阻塞状态。
重入可重入锁
下面,将上述代码中锁改为可重入锁,既把 mutex = threading.Lock() 改为 mutex = threading.RLock()
运行代码,输出为:
get lock
t1 get lock against
t2 get lock
t2 get lock against
上述代码中,线程 t1 获取锁后,再次获取锁,此时锁可以被再次获取,线程得到锁后执行操作,完成操作后释放锁。线程 t1 释放锁后,线程 t2 获取锁,执行操作。
可重入锁应用场景
现在有一个方法,为了保证数据安全,其会申请锁:
def func():
if lock.acquire():
print(threading.currentThread().name + " do something")
lock.release()
现在有另一个方法,为了保证数据安全,同样会申请锁,并且会调用上面的方法:
def func2():
if lock.acquire():
print(threading.currentThread().name + " do something")
print(threading.currentThread().name + " call func")
func()
lock.release()
此时,变量 lock 应该是一个可重入锁,否则线程调用 func() 后,将会被阻塞。