浅谈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处理cookie详解
Feb 07 Python
python轻松实现代码编码格式转换
Mar 26 Python
python使用nntp读取新闻组内容的方法
May 08 Python
总结用Pdb库调试Python的方式及常用的命令
Aug 18 Python
python将每个单词按空格分开并保存到文件中
Mar 19 Python
JavaScript中的模拟事件和自定义事件实例分析
Jul 27 Python
Python根据文件名批量转移图片的方法
Oct 21 Python
浅析使用Python搭建http服务器
Oct 27 Python
python 实现提取log文件中的关键句子,并进行统计分析
Dec 24 Python
pytorch 实现删除tensor中的指定行列
Jan 13 Python
在python下实现word2vec词向量训练与加载实例
Jun 09 Python
Python利用folium实现地图可视化
May 23 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
php set_time_limit(0) 设置程序执行时间的函数
2010/05/26 PHP
PHP彩蛋信息介绍和阻止泄漏的方法(隐藏功能)
2014/08/06 PHP
php实现将上传word文件转为html的方法
2015/06/03 PHP
PHP二维数组排序简单实现方法
2016/02/14 PHP
php使用curl并发减少后端访问时间的方法分析
2016/05/12 PHP
Laravel 解决419错误 -ajax请求错误的问题(CSRF验证)
2019/10/25 PHP
PHP pthreads v3下的Volatile简介与使用方法示例
2020/02/21 PHP
帮助避免错误的Javascript陷阱清单
2009/05/31 Javascript
JQuery与iframe交互实现代码
2009/12/24 Javascript
jQuery 打造动态下滑菜单实现说明
2010/04/15 Javascript
基于jquery的实现简单的表格中增加或删除下一行
2010/08/01 Javascript
js控制table合并具体实现
2014/02/20 Javascript
验证码在IE中不刷新而谷歌等浏览器正常的解决方案
2014/03/18 Javascript
Javascript学习笔记之 对象篇(四) : for in 循环
2014/06/24 Javascript
详解vuex 中的 state 在组件中如何监听
2017/05/23 Javascript
jQuery实现腾讯信用界面(自制刻度尺)样式
2017/08/15 jQuery
JS将网址url转化为JSON格式的方法
2018/07/02 Javascript
对vuejs的v-for遍历、v-bind动态改变值、v-if进行判断的实例讲解
2018/08/27 Javascript
小程序点赞收藏功能的实现代码示例
2018/09/07 Javascript
[55:35]VGJ.S vs Mski Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
Python 文件操作技巧(File operation) 实例代码分析
2008/08/11 Python
两个命令把 Vim 打造成 Python IDE的方法
2016/03/20 Python
python实现根据指定字符截取对应的行的内容方法
2018/10/23 Python
python selenium 弹出框处理的实现
2019/02/26 Python
Python对列表的操作知识点详解
2019/08/20 Python
Python中zipfile压缩文件模块的基本使用教程
2020/06/14 Python
澳大利亚领先的美容护肤品零售商之一:SkincareStore
2018/01/22 全球购物
Servlet方面面试题
2016/09/28 面试题
ruby如何进行集成操作?Ruby能进行多重继承吗?
2013/10/16 面试题
《浅水洼里的小鱼》听课反思
2014/02/28 职场文书
人力资源管理毕业求职信
2014/08/05 职场文书
人力资源管理求职信
2014/08/07 职场文书
让子弹飞观后感
2015/06/11 职场文书
小学秋季运动会通讯稿
2015/11/25 职场文书
Python selenium模拟网页点击爬虫交管12123违章数据
2021/05/26 Python
Android移动应用开发指南之六种布局详解
2022/09/23 Java/Android