浅谈python锁与死锁问题

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

加锁的原因

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

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

(0)

相关推荐

  • 浅谈Python线程的同步互斥与死锁

    线程间通信方法 1. 通信方法 线程间使用全局变量进行通信     2. 共享资源争夺 共享资源:多个进程或者线程都可以操作的资源称为共享资源.对共享资源的操作代码段称为临界区. 影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误.此时往往需要同步互斥机制协调操作顺序.     3. 同步互斥机制 同步 : 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作.两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行.比如 A 任务的

  • Python中死锁的形成示例及死锁情况的防止

    死锁示例 搞多线程的经常会遇到死锁的问题,学习操作系统的时候会讲到死锁相关的东西,我们用Python直观的演示一下. 死锁的一个原因是互斥锁.假设银行系统中,用户a试图转账100块给用户b,与此同时用户b试图转账200块给用户a,则可能产生死锁. 2个线程互相等待对方的锁,互相占用着资源不释放. #coding=utf-8 import time import threading class Account: def __init__(self, _id, balance, lock): sel

  • python multiprocessing多进程变量共享与加锁的实现

    python多进程和多线程是大家会重点了解的部分,因为很多工作如果并没有前后相互依赖关系的话其实顺序并不是非常的重要,采用顺序执行的话就必定会造成无谓的等待,任凭cpu和内存白白浪费,这是我们不想看到的. 为了解决这个问题,我们就可以采用多线程或者多进程的方式,(多线程我们之后再讲),而这两者之间是有本质区别的.就内存而言,已知进程是在执行过程中有独立的内存单元的,而多个线程是共享内存的,这是多进程和多线程的一大区别. 利用Value在不同进程中同步变量 在多进程中,由于进程之间内存相互是隔离的

  • Python的互斥锁与信号量详解

    并发与锁 多个线程共享数据的时候,如果数据不进行保护,那么可能出现数据不一致现象,使用锁,信号量.条件锁 互斥锁 1. 互斥锁,是使用一把锁把代码保护起来,以牺牲性能换取代码的安全性,那么Rlock后 必须要relase 解锁 不然将会失去多线程程序的优势 2. 互斥锁的基本使用规则: import threading # 声明互斥锁 lock=threading.Rlock(); def handle(sid):# 功能实现代码 lock.acquire() #加锁 # writer code

  • 利用Python+Java调用Shell脚本时的死锁陷阱详解

    前言 最近有一项需求,要定时判断任务执行条件是否满足并触发 Spark 任务,平时编写 Spark 任务时都是封装为一个 Jar 包,然后采用 Shell 脚本形式传入所需参数执行,考虑到本次判断条件逻辑复杂,只用 Shell 脚本完成不利于开发测试,所以调研使用了 Python 和 Java 分别调用 Spark 脚本的方法. 使用版本为 Python 3.6.4 及 JDK 8 Python 主要使用 subprocess 库.Python 的 API 变动比较频繁,在 3.5 之后新增了

  • Python 防止死锁的方法

    问题 你正在写一个多线程程序,其中线程需要一次获取多个锁,此时如何避免死锁问题. 解决方案 在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的.举个例子:一个线程获取了第一个锁,然后在获取第二个锁的 时候发生阻塞,那么这个线程就可能阻塞其他线程的执行,从而导致整个程序假死. 解决死锁问题的一种方案是为程序中的每一个锁分配一个唯一的id,然后只允许按照升序规则来使用多个锁,这个规则使用上下文管理器 是非常容易实现的,示例如下: import threading from contex

  • Python 共享变量加锁、释放详解

    一.共享变量 共享变量:当多个线程访问同一个变量的时候.会产生共享变量的问题. 例子: import threading sum = 0 loopSum = 1000000 def myAdd(): global sum, loopSum for i in range(1, loopSum): sum += 1 def myMinu(): global sum, loopSum for i in range(1, loopSum): sum -= 1 if __name__ == "__main

  • Python多线程编程(五):死锁的形成

    前一篇文章Python:使用threading模块实现多线程编程四[使用Lock互斥锁]我们已经开始涉及到如何使用互斥锁来保护我们的公共资源了,现在考虑下面的情况– 如果有多个公共资源,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,这会引起什么问题? 死锁概念 所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程.

  • Python Threading 线程/互斥锁/死锁/GIL锁

    导入线程包 import threading 准备函数线程,传参数 t1 = threading.Thread(target=func,args=(args,)) 类继承线程,创建线程对象 class MyThread(threading.Thread) def run(self): pass if __name__ == "__main__": t = MyThread() t.start() 线程共享全面变量,但在共享全局变量时会出现数据错误问题 使用 threading 模块中的

  • 举例讲解Python中的死锁、可重入锁和互斥锁

    一.死锁 简单来说,死锁是一个资源被多次调用,而多次调用方都未能释放该资源就会造成死锁,这里结合例子说明下两种常见的死锁情况. 1.迭代死锁 该情况是一个线程"迭代"请求同一个资源,直接就会造成死锁: import threading import time class MyThread(threading.Thread): def run(self): global num time.sleep(1) if mutex.acquire(1): num = num+1 msg = se

  • python避免死锁方法实例分析

    本文实例讲述了python避免死锁方法.分享给大家供大家参考.具体分析如下: 当两个或者更多的线程在等待资源的时候就会产生死锁,两个线程相互等待. 在本文实例中 thread1 等待thread2释放block , thread2等待thtead1释放ablock,   避免死锁的原则: 1. 一定要以一个固定的顺序来取得锁,这个列子中,意味着首先要取得alock, 然后再去block 2. 一定要按照与取得锁相反的顺序释放锁,这里,应该先释放block,然后是alock import thre

随机推荐