今天写一下工作中遇到的问题线程锁,其实过去对何时应该用线程锁有些模糊。所以工作中保险起见会在所有线程的变量修改处都加上锁,以起到保护作用。
其实这样做既盲目又增加了工作量,今天我们一同研究下线程锁的作用和应用场景。
我们还是通过一个小例子来说明:
事情是这样的,我们平时保管着2个CANoe,一个VN5640,一个VN1640,有些时候VN1640是闲置的,因为我们有了国货优品Tsmaster了,所以我们就开发一套在线CANoe租借系统,在我们CANoe有闲置的时候,外借给其他同事,提高CANoe的使用率。
下面的租借系统代码经过简单调试就上线了:
import time
import threading
#CANoe账户,用于存储管理者和余量
class CANoe_Amount():
def __init__(self, account_name, CANoe):
# 构造函数account_name管理账户名,CANoe为剩余数量
self.account_name=account_name
self.CANoe=CANoe
#借用函数
def borrow(account,draw_amount):
if account.CANoe >= draw_amount:
print(threading.current_thread().name+'借用成功,取出:'+str(draw_amount))
time.sleep(1)
account.CANoe -= draw_amount
print('剩余CANoe为:'+str(account.CANoe))
else:
print(f"{threading.current_thread().name}想借用,但{str(account.account_name)}名下的CANoe余量不足")
CANoe=CANoe_Amount('gm',1) #实例化CANoe类并挂出了一个CANoe余量
threading.Thread(name="XX",target=borrow,args=(CANoe,1)).start() #线程1 XX想借用一个
threading.Thread(name="YY",target=borrow,args=(CANoe,1)).start() #线程2 YY想借用一个
一天VN1640闲置下来了,我们就在线挂出了1个CANoe的外借余量。 Wz和Ww这两个小伙伴,同时看到了信息,并同时在线申请了借用。
threading.Thread(name="Wz",target=borrow,args=(CANoe,1)).start() #线程1 Wz想借用一个
threading.Thread(name="Ww",target=borrow,args=(CANoe,1)).start() #线程2 Ww想借用一个
结果你猜怎么了,系统提示如下信息,他俩都外借成功了这个CANoe。
Wz借用成功,取出:1
Ww借用成功,取出:1
剩余CANoe为:0 剩余CANoe为:-1
不是只挂了一个的余量,为什么两个人都借到了哪?其实问题就出在线程上,我们把Wz和Ww的申请比作两个线程,他们几乎同一时间申请获取了同一个变量的值和修改权,两个线程对同一变量来说是互相干扰的。
这样的结果,显然是违背了当初的设想的,于是我们请出了线程锁,修改后的代码如下:
import time
import threading
class CANoe_Amount():
def __init__(self,account_name, CANoe):
# 构造函数account_name账户名,CANoe为剩余数量
self.account_name=account_name
self.CANoe=CANoe
#借用函数
def borrow(account,draw_amount):
with lock: #通过上下文添加线程锁
if account.CANoe >= draw_amount:
print(threading.current_thread().name+'借用成功,取出:'+str(draw_amount))
time.sleep(1)
account.CANoe -= draw_amount
print('剩余CANoe为:'+str(account.CANoe))
else:
print(f"{threading.current_thread().name}想借用,但{str(account.account_name)}名下CANoe余量不足")
a=CANoe_Amount('Gm',1)
lock = threading.Lock()
threading.Thread(name="Wz",target=borrow,args=(a,1)).start()
threading.Thread(name="Ww",target=borrow,args=(a,1)).start()
使用了线程锁后,运行结果如下:
Wz借用成功,取出:1
剩余CANoe为:0
Ww想借用,但Gm名下的CANoe余量不足
这样的结果才是符合我们预期的,同时我们发现,加了锁以后,线程有了执行的先后,在Wz线程执行的过程中(包含1s的等待),Ww线程是完全处于阻塞等待的状态。只有Wz执行完之后,锁被释放时,Ww线程才能被执行。
总结,通过这个例子:
- 我们了解了线程锁的作用,即保证数据的一致性 ,对锁内的资源(变量)进行锁定,避免其它线程对其进行篡改。
- 通常类似于以上例子中,同一函数建立多个不同线程的Case,都建议在修改共同变量时添加锁,以防止冲突。
- 推荐使用with lock:的方式来添加锁,可以避免误操作带来的死锁,代码也比较简明。但不要嵌套使用,如果需要嵌套使用,需要换用RLOCK(LOCK的升级版)。
- 锁的应用对程序的执行速度也带来了弊端,首先创建锁需要时间,其次锁定状态下,其他带锁的线程是阻塞状态。只有一个线程是在工作的。所以要避免在锁内使用不必要的等待。
- 另外,有一点也需要注意,一个线程添加了锁,一个线程不添加,那么不添加锁的线程,是不会受到锁的限制的。
网友评论