共计 2749 个字符,预计需要花费 7 分钟才能阅读完成。
明天的文章,咱们一起来聊聊多线程场景当中不可或缺的另外一个局部——锁。
如果你学过操作系统,那么对于锁应该不生疏。锁的含意是线程锁,能够用来指定某一个逻辑或者是资源同一时刻只能有一个线程拜访。这个很好了解,就如同是有一个房间被一把锁锁住了,只有拿到钥匙的人才能进入。每一个人从房间门口拿到钥匙进入房间,出房间的时候会把钥匙再放回到门口。这样下一个到门口的人就能够拿到钥匙了。这里的房间就是某一个资源或者是一段逻辑,而拿取钥匙的人其实指的是一个线程。
加锁的起因
咱们明确了锁的原理,不禁有了一个问题,咱们为什么须要锁呢,它在哪些场景当中会用到呢?
其实它的应用场景十分广,咱们举一个非常简单的例子,就是淘宝买货色。咱们都晓得商家的库存都是无限的,卖掉一个少一个。如果说以后某个商品库存只剩下一个,但当下却有两个人同时购买。两个人同时购买也就是有两个申请同时发动购买申请,如果咱们不加锁的话,两个线程同时查问到商品的库存是 1,大于 0,进行购买逻辑之后,同时减一。因为两个线程同时执行,所以最初商品的库存会变成 -1。
显然商品的库存不应该是一个正数,所以咱们须要防止这种状况产生。通过加锁能够完满解决这个问题。咱们规定一次只能有一个线程发动购买的申请,那么这样当一个线程将库存减到 0 的时候,第二个申请就无奈批改了,就保障了数据的准确性。
代码实现
那么在 Python 当中,咱们怎么样来实现这个锁呢?
其实很简略,threading 库当中曾经为咱们提供了线程的工具,咱们间接拿过去用就能够了。咱们通过应用 threading 当中的 Lock 对象,能够很轻易的实现办法加锁的性能。
import threading
class 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 threading
class 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 threading
class 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 threading
class 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”即可收费获取,每日干货分享