浅谈python锁与死锁问题


Posted in Python onAugust 14, 2020

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

加锁的原因

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

其实它的使用场景非常广,我们举一个非常简单的例子,就是淘宝买东西。我们都知道商家的库存都是有限的,卖掉一个少一个。假如说当前某个商品库存只剩下一个,但当下却有两个人同时购买。两个人同时购买也就是有两个请求同时发起购买请求,如果我们不加锁的话,两个线程同时查询到商品的库存是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并发锁与死锁问题的详细内容,更多关于python并发锁与死锁的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
解析Python中的变量、引用、拷贝和作用域的问题
Apr 07 Python
Python读写ini文件的方法
May 28 Python
Python计算两个日期相差天数的方法示例
May 23 Python
详解Python中where()函数的用法
Mar 27 Python
python3.5安装python3-tk详解
Apr 26 Python
用uWSGI和Nginx部署Flask项目的方法示例
May 05 Python
python读取指定字节长度的文本方法
Aug 27 Python
Python中注释(多行注释和单行注释)的用法实例
Aug 28 Python
Python 装饰器@,对函数进行功能扩展操作示例【开闭原则】
Oct 17 Python
解决jupyter notebook 出现In[*]的问题
Apr 13 Python
详解基于python的全局与局部序列比对的实现(DNA)
Oct 07 Python
python实现经纬度采样的示例代码
Dec 10 Python
Python3.8安装Pygame教程步骤详解
Aug 14 #Python
Python configparser模块应用过程解析
Aug 14 #Python
PyCharm 2020.2 安装详细教程
Sep 25 #Python
Python logging模块handlers用法详解
Aug 14 #Python
Python代码注释规范代码实例解析
Aug 14 #Python
Python发送邮件实现基础解析
Aug 14 #Python
Python压缩模块zipfile实现原理及用法解析
Aug 14 #Python
You might like
动态新闻发布的实现及其技巧
2006/10/09 PHP
PHP中break及continue两个流程控制指令区别分析
2011/04/18 PHP
实现php删除链表中重复的结点
2018/09/27 PHP
laravel 创建命令行命令的图文教程
2019/10/23 PHP
PHP 自动加载类原理与用法实例分析
2020/04/14 PHP
php+js实现的拖动滑块验证码验证表单操作示例【附源码下载】
2020/05/27 PHP
侧栏跟随滚动的简单实现代码
2013/03/18 Javascript
解决Jquery load()加载GB2312页面时出现乱码的两种方案
2013/09/10 Javascript
javascript实现textarea中tab键的缩排处理方法
2015/06/26 Javascript
jQuery三级下拉列表导航菜单代码分享
2020/04/15 Javascript
聊一聊JS中的prototype
2016/09/29 Javascript
jquery删除table当前行的实例代码
2016/10/07 Javascript
微信小程序 POST请求(网络请求)详解及实例代码
2016/11/16 Javascript
在node中如何使用 ES6
2017/04/22 Javascript
JavaScript运动框架 解决防抖动问题、悬浮对联(二)
2017/05/17 Javascript
vue.js 左侧二级菜单显示与隐藏切换的实例代码
2017/05/23 Javascript
基于BootStrap的文本编辑器组件Summernote
2017/10/27 Javascript
Vue组件实现触底判断
2019/06/26 Javascript
JavaScript实现简单贪吃蛇效果
2020/03/09 Javascript
[15:39]教你分分钟做大人:龙骑士
2014/10/30 DOTA
python中lambda与def用法对比实例分析
2015/04/30 Python
Python的爬虫包Beautiful Soup中用正则表达式来搜索
2016/01/20 Python
python中多层嵌套列表的拆分方法
2018/07/02 Python
Python collections模块使用方法详解
2019/08/28 Python
Python使用Opencv实现图像特征检测与匹配的方法
2019/10/30 Python
Python通过Pillow实现图片对比
2020/04/29 Python
对python pandas中 inplace 参数的理解
2020/06/27 Python
浅谈OpenCV中的新函数connectedComponentsWithStats用法
2020/07/05 Python
马来西亚航空官方网站:Malaysia Airlines
2017/07/28 全球购物
金蝶的一道SQL笔试题
2012/12/18 面试题
医学生职业规划范文
2014/01/05 职场文书
西安交大自主招生自荐信
2014/01/27 职场文书
考核工作实施方案
2014/03/30 职场文书
借款民事起诉状范文
2015/05/19 职场文书
小学美术教学反思
2016/02/17 职场文书
2016年第104个国际护士节活动总结
2016/04/06 职场文书