Python浅谈并发锁与死锁问题

来源:千锋教育
发布时间:2023-11-06 12:00:37
分享

如果你学过操作系统,那么对于锁应该不陌生。锁的含义是线程锁,可以用来指定某一个逻辑或者是资源同一时刻只能有一个线程访问。这个很好理解,就好像是有一个房间被一把锁锁住了,只有拿到钥匙的人才能进入。每一个人从房间门口拿到钥匙进入房间,出房间的时候会把钥匙再放回到门口。这样下一个到门口的人就可以拿到钥匙了。这里的房间就是某一个资源或者是一段逻辑,而拿取钥匙的人其实指的是一个线程。

加锁的原因

我们明白了锁的原理,不禁有了一个问题,我们为什么需要锁呢,它在哪些场景当中会用到呢?

其实它的使用场景非常广,我们举一个非常简单的例子,就是淘宝买东西。我们都知道商家的库存都是有限的,卖掉一个少一个。假如说当前某个商品库存只剩下一个,但当下却有两个人同时购买。两个人同时购买也就是有两个请求同时发起购买请求,如果我们不加锁的话,两个线程同时查询到商品的库存是1,大于0,进行购买逻辑之后,减一。由于两个线程同时执行,所以最后商品的库存会变成-1。

显然商品的库存不应该是一个负数,所以我们需要避免这种情况发生。通过加锁可以完美解决这个问题。我们规定一次只能有一个线程发起购买的请求,那么这样当一个线程将库存减到0的时候,第二个请求就无法修改了,就保证了数据的准确性。

代码实现

那么在Python当中,我们怎么样来实现这个锁呢?

其实很简单,threading库当中已经为我们提供了线程的工具,我们直接拿过来用就可以了。我们通过使用threading当中的Lock对象,可以很轻易的实现方法加锁的功能。

importthreading

classPurchaseRequest:

'''

初始化库存与锁

'''

def__init__(self,initial_value=0):

self._value=initial_value

self._lock=threading.Lock()

defincr(self,delta=1):

'''

加库存

'''

self._lock.acquire()

self._value+=delta

self._lock.release()

defdecr(self,delta=1):

'''

减库存

'''

self._lock.acquire()

self._value-=delta

self._lock.release()

我们从代码当中就可以很轻易的看出Lock这个对象的使用方法,我们在进入加锁区(资源抢占区)之前,我们需要先使用lock.acquire()方法获取锁。Lock对象可以保证同一时刻只能有一个线程获取锁,只有获取了锁之后才会继续往下执行。当我们执行完成之后,我们需要把锁“放回门口”,所以需要再调用一下release方法,表示锁的释放。

这里有一个小问题是很多程序员在编程的时候总是会忘记release,导致不必要的bug,而且这种分布式场景当中的bug很难通过测试发现。因为测试的时候往往很难测试并发场景,codereview的时候也很容易忽略,因此一旦泄露了还是挺难发现的。

为了解决这个问题,Lock还提供了一种改进的用法,就是使用with语句。with语句我们之前在使用文件的时候用到过,使用with可以替我们完成trycatch以及资源回收等工作,我们只管用就完事了。这里也是一样,使用with之后我们就可以不用管锁的申请和释放了,直接写代码就行,所以上面的代码可以改写成这样:

importthreading

classPurchaseRequest:

'''

初始化库存与锁

'''

def__init__(self,initial_value=0):

self._value=initial_value

self._lock=threading.Lock()

defincr(self,delta=1):

'''

加库存

'''

withself._lock:

self._value+=delta

defdecr(self,delta=1):

'''

减库存

'''

withself._lock:

self._value-=delta

这样看起来是不是清爽很多?

可重入锁

上面介绍的只是最简单的锁,我们经常使用的往往是可重入锁。

什么叫可重入锁呢?简单解释一下,就是在一个线程已经持有了锁的情况下,它可以再次进入被加锁的区域。但是既然线程还持有锁没有释放,那么它不应该还是在加锁区域吗,怎么会有需要再次进入被加锁区域的情况呢?其实是有的,道理也很简单,就是递归。

我们把上面的例子稍微改一点点,就完全不一样了。

importthreading

classPurchaseRequest:

'''

初始化库存与锁

'''

def__init__(self,initial_value=0):

self._value=initial_value

self._lock=threading.Lock()

defincr(self,delta=1):

'''

加库存

'''

withself._lock:

self._value+=delta

defdecr(self,delta=1):

'''

减库存

'''

withself._lock:

self.incr(-delta)

我们关注一下上面的decr方法,我们用incr来代替了原本的逻辑实现了decr。但是有一个问题是decr也是一个加锁的方法,需要前一个锁释放了才能进入。但它已经持有了锁了,那么这种情况下就会发生死锁。

我们只需要把Lock换成可重入锁就可以解决这个问题,只需要修改一行代码。

importthreading

classPurchaseRequest:

'''

初始化库存与锁

我们使用RLock代替了Lock,也可重入锁代替了普通锁

'''

def__init__(self,initial_value=0):

self._value=initial_value

self._lock=threading.RLock()

defincr(self,delta=1):

'''

加库存

'''

withself._lock:

self._value+=delta

defdecr(self,delta=1):

'''

减库存

'''

withself._lock:

self.incr(-delta)

以上内容为大家介绍了Python浅谈并发锁与死锁问题,希望对大家有所帮助,如果想要了解更多Python相关知识,请关注IT培训机构:千锋教育。

声明:本站部分稿件版权来源于网络,如有侵犯版权,请及时联系我们。

相关推荐

  • Python中的文件管理 文件管理是很多应用程序的基本功能和重要组成部分。Python可以使文件管理极其简单,特别是和其它语言相对比。以下,PeytonMcCullough讲解了文件管理基础。介绍你玩过的游戏使用文件来保存存档
  • Python的函数式编程与闭包 函数式编程      函数式编程这个概念我们可能或多或少都听说过,刚听说的时候不明觉厉,觉得这是一个非常黑科技的概念。但是实际上它的含义很朴
  • Python的内存管理机制 Python的内存管理机制对于工程师而言,内存管理机制非常重要,是绕不过去的一环。如果你是Java工程师,面试的时候一定会问JVM。C++工程师也一定会问内存泄漏,同样我们想要深入学习Python,内
  • Python 多线程死锁问题的巧妙解决方法 死锁死锁的原理非常简单,用一句话就可以描述完。就是当多线程访问多个锁的时候,不同的锁被不同的线程持有,它们都在等待其他线程释放出锁来,于是便陷入了永久等待。比如A线程持有1号锁,等待2号锁,B线程持有
  • Python在自动化测试系统中的应用 自动化测试系统在敏捷开发、持续集成和持续交付中起着非常重要的作用。它对加快新功能发布,缩短现场问题解决时间,提升用户感受度,乃至提高企业竞争力都至关重要。本文结合笔者在自动化测试系统建设中的实践,具体
  • Python导入模块时的过程 python的import是在程序运行期间执行的,并非像其它很多语言一样是在编译期间执行。也就是说,import可以出现在任何地方,只有执行到这个import行时,才会执行导入操作。且在import某