Python threading模块中Lock与Rlock的使用详细讲解
本文详细解析Python threading模块中的Lock与RLock机制。通过对比未加锁与加锁场景,说明Lock如何保证共享变量互斥访问,并指出其连续release可能导致死锁的问题。重点讲解RLock的可重入特性及计数器原理,提供完整代码示例展示如何避免死锁并确保线程安全。
前言
在多线程应用开发中,确保线程安全、实现线程间同步以及正确处理共享变量访问是核心挑战。若处理不当,可能引发严重的数据竞争或程序崩溃。Python的threading模块提供了Lock、RLock、Semaphore、Event和Condition等同步原语,旨在解决线程同步及共享变量互斥访问的问题。
- Lock与RLock:用于解决多线程访问共享变量的互斥问题
- Semaphore对象:作为Lock的增强版,允许同时被多个线程持有,而Lock仅允许单一线程持有。
- Event对象:一种线程间通信机制,充当信号角色,允许一个线程向另一个线程发送信号以触发特定操作。
- Condition对象:允许在满足特定事件或条件时才处理数据,提供更复杂的同步控制。
1、Lock(互斥锁)
请求锁定 -> 进入锁定池等待 -> 获取锁 -> 已锁定 -> 释放锁
Lock(指令锁)是最低级的同步指令。当Lock处于锁定状态时,它不属于任何特定线程。Lock包含两种状态:锁定和非锁定,以及两个基本操作方法。
可以将Lock视为拥有一个锁定池。当线程请求锁定时,该线程进入池中等待,直到获得锁定后出池。池中的线程处于同步阻塞状态。
构造方法:mylock = threading.Lock()
实例方法:
- acquire([timeout]):使线程进入同步阻塞状态,尝试获取锁定。
- release():释放锁。调用前线程必须已获得锁定,否则将抛出异常。
实例一(未使用锁的情况):
import threading
import time
num = 0
def show(arg):
global num
time.sleep(1)
num +=1
print('bb :{}'.format(num))
for i in range(5):
t = threading.Thread(target=show, args=(i,)) # 注意传入参数后一定要有【,】逗号
t.start()
print('main thread stop')
main thread stop bb :1 bb :2 bb :3bb :4 bb :5
实例二(使用锁的情况)
import threading
import time
num = 0
lock = threading.RLock()
# 调用acquire([timeout])时,线程将一直阻塞,
# 直到获得锁定或者直到timeout秒后(timeout参数可选)。
# 返回是否获得锁。
def Func():
lock.acquire()
global num
num += 1
time.sleep(1)
print(num)
lock.release()
for i in range(10):
t = threading.Thread(target=Func)
t.start()
1 2 3 4 5 6 7 8 9 10 #可以看出,全局变量在每次被调用时都要获得锁,才能操作,因此保证了共享数据的安全性
对于Lock对象,如果一个线程连续两次调用release,会导致死锁。因此,Lock较少直接使用,通常采用RLock进行线程锁设定。
import threading
import time
class MyThread(threading.Thread):
def run(self):
global num
time.sleep(1)
if lock.acquire(1):
num = num+1
msg = self.name+' set num to '+str(num)
print(msg)
lock.acquire()
lock.release()
lock.release()
num = 0
lock = threading.Lock()
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test()
Thread-12 set num to 12、RLock(可重入锁)
RLock(可重入锁)是一种允许同一线程多次请求的同步指令。RLock引入了“拥有线程”和“递归等级”的概念。当处于锁定状态时,RLock被特定线程拥有。拥有RLock的线程可以再次调用acquire(),但释放锁时需调用相同次数的release()。可认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用acquire()/release(),计数器+1/-1;当计数器为0时,锁处于未锁定状态。
- 构造方法:mylock = threading.RLock()
- 实例方法:acquire([timeout])/release():与Lock类似。
实例解决死锁问题,通过调用相同次数的acquire和release,保证操作成对出现。
'''
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
import threading
rLock = threading.RLock() #RLock对象
rLock.acquire()
rLock.acquire() #在同一线程内,程序不会堵塞。
rLock.release()
rLock.release()
print(rLock.acquire())
详细实例:
import threading
mylock = threading.RLock()
num = 0
class WorkThread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.t_name = name
def run(self):
global num
while True:
mylock.acquire()
print('\n%s locked, number: %d' % (self.t_name, num))
if num >= 2:
mylock.release()
print('\n%s released, number: %d' % (self.t_name, num))
break
num += 1
print('\n%s released, number: %d' % (self.t_name, num))
mylock.release()
def test():
thread1 = WorkThread('A-Worker')
thread2 = WorkThread('B-Worker')
thread1.start()
thread2.start()
if __name__ == '__main__':
test() A-Worker locked, number: 0
A-Worker released, number: 1
A-Worker locked, number: 1
A-Worker released, number: 2
A-Worker locked, number: 2
A-Worker released, number: 2
B-Worker locked, number: 2
B-Worker released, number: 2
到此这篇关于Python threading模块中lock与Rlock的使用详细讲解的文章就介绍到这了,更多相关Python threading内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













