本文始发于个人公众号:TechFlow,原创不易,求个关注

今天是Python专题的第24篇文章,我们一起来聊聊多线程场景当中不可或缺的另外一个部分——

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

加锁的原因

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

其实它的使用场景非常广,我们举一个非常简单的例子,就是淘宝买东西。我们都知道商家的库存都是有限的,卖掉一个少一个。假如说当前某个商品库存只剩下一个,但当下却有两个人同时购买。两个人同时购买也就是有两个请求同时发起购买请求,如果我们不加锁的话,两个线程同时查询到商品的库存是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还提供了其他的解决方案,我们放到下一篇文章当中再和大家分享。

今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。

- END -

Python | 浅谈并发锁与死锁问题的更多相关文章

  1. python浅谈正则的常用方法

    python浅谈正则的常用方法覆盖范围70%以上 上一次很多朋友写文字屏蔽说到要用正则表达,其实不是我不想用(我正则用得不是很多,看过我之前爬虫的都知道,我直接用BeautifulSoup的网页标签去 ...

  2. Python 浅谈注释的重要性

    最近参加了一个比赛,然后看到队友编程的代码,我觉得真的是难以下咽,几乎每个字符都要咨询他,用老师的话来说,这就是山炮编程员,所以此时的我意识到写一篇关于注释程序的重要性了,因此特地的写一篇文章帮助大家 ...

  3. Python 浅谈编程规范和软件开发目录规范的重要性

    最近参加了一个比赛,然后看到队友编程的代码,我觉得真的是觉得注释和命名规范的重要性了,因为几乎每个字符都要咨询他,用老师的话来说,这就是命名不规范的后续反应.所以此时的我意识到写一篇关于注释程序的重要 ...

  4. 你用对锁了吗?浅谈 Java “锁” 事

    每个时代,都不会亏待会学习的人 大家好,我是yes. 本来打算继续写消息队列的东西的,但是最近在带新同事,发现新同事对于锁这方面有一些误解,所以今天就来谈谈"锁"事和 Java 中 ...

  5. 浅谈MS-SQL锁机制

    锁的概述 一. 为什么要引入锁 多个用户同时对数据库的并发操作时会带来以下数据不一致的问题: 丢失更新A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统 脏 ...

  6. python同步、互斥锁、死锁

    目录 同步 同步的概念 解决线程同时修改全局变量的方式 互斥锁 使用互斥锁完成2个线程对同一个全局变量各加9999999 次的操作 上锁解锁过程 总结 死锁 避免死锁 同步 同步的概念 同步就是协同步 ...

  7. JDK1.5新特性,基础类库篇,浅谈并发工具包(Concurrency Utilities)

    java.util.concurrent, java.util.concurrent.atomic, 和 java.util.concurrent.locks 包提供了高性能的.可扩展的框架,保证开发 ...

  8. 由浅入深——从ArrayList浅谈并发容器

    原创作品转载请附:https://www.cnblogs.com/superlsj/p/11655523.html 一.一个案例引发的思考 public class ArrayListTest { p ...

  9. python浅谈编程规范和软件开发目录规范的重要性

    前言 我们这些初学者,目前要做的就是遵守代码规范,这是最基本的,而且每个团队的规范可能还不一样,以后工作了,尽可能和团队保持一致,目前初学者就按照官方的要求即可 新人进入一个企业,不会接触到核心的架构 ...

随机推荐

  1. 题解 CF 1372 B

    题目 传送门 题意 给出 \(n\),输出 \(a\) ,\(b\) (\(0 < a \leq b < n\)),使\(a+b=n\)且 \(\operatorname{lcm}(a,b ...

  2. grpc 之 word2pdf使用

    ​ 做一个word转pdf的服务,采用grpc,使用libreoffice命令. 1.构建libreoffice镜像 FROM python:3.6 ENV TZ=Asia/Shanghai RUN ...

  3. 推荐IT经理/产品经理,常用工具和网站

    一.  常用必备工具 1)文档工具 石墨文档,在线协作文档工具 https://shimo.im/ 2) 表格工具 麦客,在线问卷调查工具 http://www.mikecrm.com/ 3)脑图工具 ...

  4. Jenkins怎么安装?Jenkins控制台输出乱码怎么处理?Jenkins执行selenium脚本时浏览器不显示怎么处理?

    今天我们来看一看Jenkins的安装. 首先我们看一下Jenkins是什么,能够干什么.Jenkins呢是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开 ...

  5. 线上CUP负载过高排查方法

      1.top命令查看线程占据的CPU 注意:上面行的cpu是多个内核的平均CPU,不可能超过100% 下面的cpu是每个进程实际占用的cpu,可能超过100% 备注:查看多个内核cpu,只需要在输入 ...

  6. 2020年最新.NET面试题

    .net 面试题最新版   2020-2-26 每日几道面试题1. .NET和C#有什么区别答:.NET一般指 .NET FrameWork框架,它是一种平台,一种技术.C#是一种编程语言,可以基于. ...

  7. IDEA中搭建项目环境

    ladies and gentlemen,Welcome to my blog! 本文主要在IDEA中搭建项目环境. 有问题和指正,欢迎下方留言~ 1. 使用GitLab将项目下载下来   1.1 选 ...

  8. 爬虫(三)-之Urllib库的基本使用

    什么是Urllib Urllib是python内置的HTTP请求库 包括以下模块 urllib.request 请求模块 urllib.error 异常处理模块 urllib.parse   url解 ...

  9. 《吊打面试官》系列-Redis基础知识

    前言Redis在互联网技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在Redis的使用和原理方面对小伙伴们进行360°的刁难.作为一个在互联网公司面一次拿一次offer的面霸(请允许我使用一下 ...

  10. lua中 string.find(查找获取字符串) string.gsub(查找替换字符串) string.sub(截取字符串)

    > aaa='/p/v2/api/winapi/adapter/lgj'> print(string.find(aaa, "^/.+/adapter/(.*)"))1 ...