可重入锁
为了避免因同一线程多次使用互斥锁造成的死锁,threading模块中提供了RLock类。RLock类代表可重入锁,它允许同一线程多次锁定和多次释放。通过RLock类的构造方法可以创建一个可重入锁,示例如下:
r_lock = RLock()
RLock类中包含以下三个重要的属性:
(1) _block,表示内部的互斥锁。
(2) _owner,表示可重入锁的持有者的线程ID。
(3) _count,表示计数器,用于记录锁被持有的次数。针对RLock对象的持有线程(属主线程),每上锁一次计数器就+1,每解锁一次就-1。若计数器为0,则释放内部的锁,这时其他线程可以获取内部的互斥锁,继而获取RLock对象。
可重入锁的实现原理是通过为每个内部锁关联计数器和属主线程。当计数器为0时,内部锁处于非锁定状态,可以被其它线程持有;当线程持有一个处于非锁定状态的锁时,它将被记录为锁的持有线程,计数器置为1。
RLock类中使用acquire()和release()方法锁定和释放,具体用法与Lock类相似,此处不再过多赘述。
下面自定义一个线程类MyThread,在该类重写的run()方法中将可重入锁多次上锁定和释放,并在锁定期间修改全局变量num的值,之后启动五个线程执行程序,代码如下:
1 from threading import Thread, RLock
2 import time
3 num = 0 # 定义全局变量
4 r_lock = RLock() # 创建可重入锁r_lock
5 class MyThread(Thread):
6 def run(self):
7 global num
8 time.sleep(1)
9 if r_lock.acquire(): # 若r_lock处于锁定状态
10 num = num + 1 # 修改全局变量
11 msg = self.name + '将num改为' + str(num)
12 print(msg)
13 r_lock.acquire() # 再次上锁
14 r_lock.release() # r_lock解锁
15 r_lock.release() # r_lock再次解锁
16 if __name__ == '__main__':
17 for i in range(5): # 创建5个线程
18 t = MyThread()
19 t.start()
上述代码中,第3、4行分别定义一个全局变量num和全局可重入锁r_lock;第5~15行定义了一个线程类MyThread,在该类重写的run()方法中判断可重入锁的状态,若该锁已经处于锁定状态,首先将全局变量num的值加1后打印,然后对r_lock上锁,最后连续调用两次release()方法解锁。第17~19行创建了5个线程执行任务。
运行代码,结果如下所示:
Thread-1将num改为1
Thread-3将num改为2
Thread-2将num改为3
Thread-5将num改为4
Thread-4将num改为5
由以上结果可知,线程Thread-1优先执行,将num的值改为1;线程Thread-3第二个执行,将num的值改为2......线程Thread-4最后执行,将num的值改为5。由此表明,可重入锁同样能够解决多线程访问共享数据的冲突问题。