明天的文章,咱们一起来聊聊多线程场景当中不可或缺的另外一个局部——锁。
如果你学过操作系统,那么对于锁应该不生疏。锁的含意是线程锁,能够用来指定某一个逻辑或者是资源同一时刻只能有一个线程拜访。这个很好了解,就如同是有一个房间被一把锁锁住了,只有拿到钥匙的人才能进入。每一个人从房间门口拿到钥匙进入房间,出房间的时候会把钥匙再放回到门口。这样下一个到门口的人就能够拿到钥匙了。这里的房间就是某一个资源或者是一段逻辑,而拿取钥匙的人其实指的是一个线程。
加锁的起因
咱们明确了锁的原理,不禁有了一个问题,咱们为什么须要锁呢,它在哪些场景当中会用到呢?
其实它的应用场景十分广,咱们举一个非常简单的例子,就是淘宝买货色。咱们都晓得商家的库存都是无限的,卖掉一个少一个。如果说以后某个商品库存只剩下一个,但当下却有两个人同时购买。两个人同时购买也就是有两个申请同时发动购买申请,如果咱们不加锁的话,两个线程同时查问到商品的库存是1,大于0,进行购买逻辑之后,同时减一。因为两个线程同时执行,所以最初商品的库存会变成-1。
显然商品的库存不应该是一个正数,所以咱们须要防止这种状况产生。通过加锁能够完满解决这个问题。咱们规定一次只能有一个线程发动购买的申请,那么这样当一个线程将库存减到0的时候,第二个申请就无奈批改了,就保障了数据的准确性。
代码实现
那么在Python当中,咱们怎么样来实现这个锁呢?
其实很简略,threading库当中曾经为咱们提供了线程的工具,咱们间接拿过去用就能够了。咱们通过应用threading当中的Lock对象, 能够很轻易的实现办法加锁的性能。
import threadingclass PurchaseRequest: ''' 初始化库存与锁 ''' def __init__(self, initial_value = 0): self._value = initial_value self._lock = threading.Lock() def incr(self,delta=1): ''' 加库存 ''' self._lock.acquire() self._value += delta self._lock.release() def decr(self,delta=1): ''' 减库存 ''' self._lock.acquire() self._value -= delta self._lock.release()
咱们从代码当中就能够很轻易的看出Lock这个对象的应用办法,咱们在进入加锁区(资源抢占区)之前,咱们须要先应用lock.acquire()办法获取锁。Lock对象能够保障同一时刻只能有一个线程获取锁,只有获取了锁之后才会持续往下执行。当咱们执行实现之后,咱们须要把锁“放回门口”,所以须要再调用一下release办法,示意锁的开释。
这里有一个小问题是很多程序员在编程的时候总是会遗记release,导致不必要的bug,而且这种分布式场景当中的bug很难通过测试发现。因为测试的时候往往很难测试并发场景,code review的时候也很容易疏忽,因而一旦泄露了还是挺难发现的。
为了解决这个问题,Lock还提供了一种改良的用法,就是应用with语句。with语句咱们之前在应用文件的时候用到过,应用with能够替咱们实现try catch以及资源回收等工作,咱们只管用就完事了。这里也是一样,应用with之后咱们就能够不必管锁的申请和开释了,间接写代码就行,所以下面的代码能够改写成这样:
import threadingclass PurchaseRequest: ''' 初始化库存与锁 ''' def __init__(self, initial_value = 0): self._value = initial_value self._lock = threading.Lock() def incr(self,delta=1): ''' 加库存 ''' with self._lock: self._value += delta def decr(self,delta=1): ''' 减库存 ''' with self._lock: self._value -= delta
这样看起来是不是清新很多?
可重入锁
下面介绍的只是最简略的锁,咱们常常应用的往往是可重入锁。
什么叫可重入锁呢?简略解释一下,就是在一个线程曾经持有了锁的状况下,它能够再次进入被加锁的区域。然而既然线程还持有锁没有开释,那么它不应该还是在加锁区域吗,怎么会有须要再次进入被加锁区域的状况呢?其实是有的,情理也很简略,就是递归。
咱们把下面的例子略微改一点点,就齐全不一样了。
import threadingclass PurchaseRequest: ''' 初始化库存与锁 ''' def __init__(self, initial_value = 0): self._value = initial_value self._lock = threading.Lock() def incr(self,delta=1): ''' 加库存 ''' with self._lock: self._value += delta def decr(self,delta=1): ''' 减库存 ''' with self._lock: self.incr(-delta)
咱们关注一下下面的decr办法,咱们用incr来代替了本来的逻辑实现了decr。然而有一个问题是decr也是一个加锁的办法,须要前一个锁开释了能力进入。但它曾经持有了锁了,那么这种状况下就会产生死锁。
咱们只须要把Lock换成可重入锁就能够解决这个问题,只须要批改一行代码。
import threadingclass PurchaseRequest: ''' 初始化库存与锁 咱们应用RLock代替了Lock,也可重入锁代替了一般锁 ''' def __init__(self, initial_value = 0): self._value = initial_value self._lock = threading.RLock() def incr(self,delta=1): ''' 加库存 ''' with self._lock: self._value += delta def decr(self,delta=1): ''' 减库存 ''' with self._lock: self.incr(-delta)
总结
明天咱们的文章介绍了Python当中锁的应用办法,以及可重入锁的概念。在并发场景下开发和调试都是一个比拟艰难的工作,略微不小心就会踩到各种各样的坑,死锁只是其中一种比拟常见并且比拟容易解决的问题,除此之外还有很多其余各种各样的问题。
以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈 ,发送 “J” 即可收费获取,每日干货分享