今天的文章,我们来说说多线程场景中另一个不可或缺的部分——锁。如果你学过操作系统,你应该对锁不陌生。锁的含义是线程锁,可以用来指定某个逻辑或者某个资源一次只能被一个线程访问。这个很好理解,就好像有一个房间被一把锁锁住了,只有有钥匙的人才能进入。每个人从房间门口拿到钥匙进入房间,离开房间时把钥匙放回门口。这样下一个到门的人就可以拿到钥匙。这里的房间就是某个资源或者一段逻辑,拿钥匙的人其实指的是一个线程。加锁的缘由了解了加锁的原理,不禁会有一个疑问,为什么需要加锁,又会在哪些场景下使用呢?其实它的使用场景非常广泛。举个很简单的例子,就是在淘宝上购物。我们都知道商家的库存是有限的,少卖一件。假设目前库存中只剩下一件商品,但同时有两个人购买。两人同时购买,即有两个请求同时发起购买请求。如果我们不加锁,两个线程同时查询商品库存为1,大于0,采购逻辑执行完后,会同时减一。由于两个线程同时执行,所以最后一项的库存会变成-1。显然物品的库存不应该是负数,所以我们需要避免这种情况的发生。这个问题可以通过加锁来完美解决。我们规定每次只有一个线程可以发起一个采购请求,这样当一个线程将库存降为0时,第二个请求就不能修改了,保证了数据的准确性。代码实现那么在Python中,我们如何实现这个锁呢?其实很简单。线程库已经为我们提供了线程工具,我们可以直接使用。通过在threading中使用Lock对象,我们可以轻松实现方法锁定功能。importthreadingclassPurchaseRequest:'''初始化库存并锁定'''def__init__(self,initial_value=0):self._value=initial_valueself._lock=threading.Lock()defincr(self,delta=1):'''添加库存'''self._lock.acquire()self._value+=deltaself._lock.release()defdecr(self,delta=1):'''减少库存'''self._lock.acquire()self._value-=deltaself._lock.release()我们很容易从代码中看出如何使用Lock对象。在我们进入锁区(资源抢占区)之前,我们需要使用lock.acquire()方法获取锁。Lock对象可以保证同一时刻只有一个线程可以获取到锁,获取到锁后才会继续执行。当我们执行完后,需要“把锁放回门上”,所以需要再次调用release方法来表示释放锁。这里有个小问题就是很多程序员在编程的时候总是忘记release,导致不必要的bug,而这种分布式场景下的bug是很难通过测试发现的。因为测试的时候并发场景往往很难测试,codereview的时候也很容易被忽略,所以一旦泄露了,是相当难找的。为了解决这个问题,Lock还提供了一种改进的用法,就是使用with语句。我们之前在使用文件的时候用过with语句。使用with可以为我们完成trycatch和资源回收的工作。我们只是使用它,我们就完成了。这里也是一样。使用with后,我们可以忽略锁的申请和释放,直接写代码,所以上面的代码可以改写为:importthreadingclassPurchaseRequest:'''Initializeinventoryandlocks'''def__init__(self,initial_value=0):self._value=initial_valueself._lock=threading.Lock()defincr(self,delta=1):'''plusinventory'''withself._lock:self._value+=deltadefdecr(self,delta=1):'''减少库存'''withself._lock:self._value-=delta是不是看起来清爽多了?上面介绍的可重入锁只是最简单的锁,我们经常会用到可重入锁。什么是可重入锁?一个简单的解释就是,当一个线程已经持有锁时,它可以再次进入锁定区域。但是既然线程还持有锁并没有释放,那不应该还在锁区吗?怎么可能需要再次进入禁区?其实是有的,而且原因很简单,就是递归。如果我们稍微改变一下上面的例子,那就完全不一样了。importthreadingclassPurchaseRequest:'''初始化库存并锁定'''def__init__(self,initial_value=0):self._value=initial_valueself._lock=threading.Lock()defincr(self,delta=1):'''''增加库存'''withself._lock:self._value+=deltadefdecr(self,delta=1):'''减少库存'''withself._lock:self.incr(-delta)大家注意在上面的decr方法中,我们使用incr来代替原来的逻辑来实现decr。但是有一个问题,decr也是一种加锁方式,需要先释放,才能进入之前的锁。但是它已经持有了锁,那么这种情况下就会发生死锁。我们只需要将Lock换成可重入锁就可以解决这个问题,只需要修改一行代码。importthreadingclassPurchaseRequest:'''Initializeinventoryandlock我们用RLock代替Lock,用可重入锁代替普通锁'''def__init__(self,initial_value=0):self._value=initial_valueself._lock=threading.RLock()defincr(self,delta=1):'''添加库存'''withself._lock:self._value+=deltadefdecr(self,delta=1):'''减少库存'''withself._lock:self.incr(-delta)总结今天的文章介绍一下锁在Python中的使用以及可重入锁的概念。并发场景下的开发和调试是比较困难的工作。一不小心就会踩到各种坑。死锁只是比较常见和比较容易解决的问题之一。此外,还有很多其他各种各样的问题。以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,发“J”免费领取,每日干货分享
