python 多线程死锁问题的解决方案


Posted in Python onAugust 25, 2020

死锁的原理非常简单,用一句话就可以描述完。就是当多线程访问多个锁的时候,不同的锁被不同的线程持有,它们都在等待其他线程释放出锁来,于是便陷入了永久等待。比如A线程持有1号锁,等待2号锁,B线程持有2号锁等待1号锁,那么它们永远也等不到执行的那天,这种情况就叫做死锁。

关于死锁有一个著名的问题叫做哲学家就餐问题,有5个哲学家围坐在一起,他们每个人需要拿到两个叉子才可以吃饭。如果他们同时拿起自己左手边的叉子,那么就会永远等待右手边的叉子释放出来。这样就陷入了永久等待,于是这些哲学家都会饿死。

python 多线程死锁问题的解决方案

这是一个很形象的模型,因为在计算机并发场景当中,一些资源的数量往往是有限的。很有可能出现多个线程抢占的情况,如果处理不好就会发生大家都获取了一个资源,然后在等待另外的资源的情况。

对于死锁的问题有多种解决方法,这里我们介绍比较简单的一种,就是对这些锁进行编号。我们规定当一个线程需要同时持有多个锁的时候,必须要按照序号升序的顺序对这些锁进行访问。通过上下文管理器我们可以很容易实现这一点。

上下文管理器

首先我们来简单介绍一下上下文管理器,上下文管理器我们其实经常使用,比如我们经常使用的 with语句 就是一个上下文管理器的经典使用。当我们通过with语句打开文件的时候,它会自动替我们处理好文件读取之后的关闭以及抛出异常的处理,可以节约我们大量的代码。

同样我们也可以自己定义一个上下文处理器,其实很简单,我们只需要实现__enter__和__exit__这两个函数即可。__enter__函数用来实现进入资源之前的操作和处理,那么显然__exit__函数对应的就是使用资源结束之后或者是出现异常的处理逻辑。有了这两个函数之后,我们就有了自己的上下文处理类了。

我们来看一个样例:

class Sample:
  def __enter__(self):
    print('enter resources')
    return self
  
  def __exit__(self, exc_type, exc_val, exc_tb):
    print('exit')
    # print(exc_type)
    # print(exc_val)
    # print(exc_tb)

  def doSomething(self):
    a = 1/1
    return a

def getSample():
  return Sample()

if __name__ == '__main__':
  with getSample() as sample:
    print('do something')
    sample.doSomething()

当我们运行这段代码的时候,屏幕上打印的结果和我们的预期是一致的。

python 多线程死锁问题的解决方案

我们观察一下__exit__函数,会发现它的参数有4个,后面的三个参数对应的是抛出异常的情况。type对应异常的类型,val对应异常时的输出值,trace对应异常抛出时的运行堆栈。这些信息都是我们排查异常的时候经常需要用到的信息,通过这三个字段,我们可以根据我们的需要对可能出现的异常进行自定义的处理。

实现上下文管理器并不一定要通过类实现,Python当中也提供了上下文管理的注解,通过使用注解我们可以很方便地实现上下文管理。我们同样也来看一个例子:

import time
from contextlib import contextmanager

@contextmanager
def timethis(label):
  start = time.time()
  try:
    yield
  finally:
    end = time.time()
    print('{}: {}'.format(label, end - start))
    
    
with timethis('timer'):
  pass

在这个方法当中yield之前的部分相当于__enter__函数,yield之后的部分相当于__exit__。如果出现异常会在try语句当中抛出,那么我们编写except对异常进行处理即可。

避免死锁

了解了上下文管理器之后,我们要做的就是 在lock的外面包装一层 ,使得我们在获取和释放锁的时候可以根据我们的需要,对锁进行排序,按照升序的顺序进行持有。

这段代码源于Python的著名进阶书籍《Python cookbook》,非常经典:

from contextlib import contextmanager

# 用来存储local的数据
_local = threading.local()

@contextmanager
def acquire(*locks):
 # 对锁按照id进行排序
  locks = sorted(locks, key=lambda x: id(x))

  # 如果已经持有锁当中的序号有比当前更大的,说明策略失败
  acquired = getattr(_local,'acquired',[])
  if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
    raise RuntimeError('Lock Order Violation')

  # 获取所有锁
  acquired.extend(locks)
  _local.acquired = acquired

  try:
    for lock in locks:
      lock.acquire()
    yield
  finally:
    # 倒叙释放
    for lock in reversed(locks):
      lock.release()
    del acquired[-len(locks):]

这段代码写得非常漂亮,可读性很高,逻辑我们都应该能看懂,但是有一个小问题是这里用到了 threading.local 这个组件。

它是一个多线程场景当中的 共享变量 ,虽然说是共享的,但是对于每个线程来说读取到的值都是独立的。听起来有些难以理解,其实我们可以将它理解成一个dict,dict的key是每一个线程的id,value是一个存储数据的dict。每个线程在访问local变量的时候,都相当于先通过线程id获取了一个独立的dict,再对这个dict进行的操作。

看起来我们在使用的时候直接使用了_local,这是因为通过线程id先进行查询的步骤在其中封装了。不明就里的话可能会觉得有些难以理解。

我们再来看下这个acquire的使用:

x_lock = threading.Lock()
y_lock = threading.Lock()

def thread_1():
  while True:
    with acquire(x_lock, y_lock):
      print('Thread-1')

def thread_2():
  while True:
    with acquire(y_lock, x_lock):
      print('Thread-2')

t1 = threading.Thread(target=thread_1)
t1.start()

t2 = threading.Thread(target=thread_2)
t2.start()

运行一下会发现没有出现死锁的情况,但如果我们把代码稍加调整,写成这样,那么就会触发异常了。

def thread_1():
  while True:
    with acquire(x_lock):
      with acquire(y_lock):
       print('Thread-1')

def thread_2():
  while True:
    with acquire(y_lock):
      with acquire(x_lock):
       print('Thread-1')

因为我们把锁写成了层次结构,这样就没办法进行排序保证持有的有序性了,那么就会触发我们代码当中定义的异常。

最后我们再来看下哲学家就餐问题,通过我们自己实现的acquire函数我们可以非常方便地解决他们死锁吃不了饭的问题。

import threading

def philosopher(left, right):
  while True:
    with acquire(left,right):
       print(threading.currentThread(), 'eating')

# 叉子的数量
NSTICKS = 5
chopsticks = [threading.Lock() for n in range(NSTICKS)]

for n in range(NSTICKS):
  t = threading.Thread(target=philosopher,
             args=(chopsticks[n],chopsticks[(n+1) % NSTICKS]))
  t.start()

关于死锁的问题,对锁进行排序 只是其中的一种解决方案 ,除此之外还有很多解决死锁的模型。比如我们可以让线程在尝试持有新的锁失败的时候主动放弃所有目前已经持有的锁,比如我们可以设置机制检测死锁的发生并对其进行处理等等。发散出去其实有很多种方法,这些方法起作用的原理各不相同,其中涉及大量操作系统的基础概念和知识,感兴趣的同学可以深入研究一下这个部分,一定会对操作系统以及锁的使用有一个深刻的认识。

以上就是python 多线程死锁问题的解决方案的详细内容,更多关于python 多线程死锁的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python输入整条数据分割存入数组的方法
Nov 13 Python
Python 微信之获取好友昵称并制作wordcloud的实例
Feb 21 Python
Python3爬虫之自动查询天气并实现语音播报
Feb 21 Python
python爬虫爬取微博评论案例详解
Mar 27 Python
python求绝对值的三种方法小结
Dec 04 Python
Python 寻找局部最高点的实现
Dec 05 Python
浅谈pytorch、cuda、python的版本对齐问题
Jan 15 Python
Python编程快速上手——Excel到CSV的转换程序案例分析
Feb 28 Python
Python中使用socks5设置全局代理的方法示例
Apr 15 Python
tensorflow 20:搭网络,导出模型,运行模型的实例
May 26 Python
Python内置异常类型全面汇总
May 28 Python
利用Python网络爬虫爬取各大音乐评论的代码
Apr 13 Python
详解Pycharm与anaconda安装配置指南
Aug 25 #Python
Python如何爬取51cto数据并存入MySQL
Aug 25 #Python
基于Python爬取51cto博客页面信息过程解析
Aug 25 #Python
Python使用requests模块爬取百度翻译
Aug 25 #Python
Python爬虫使用bs4方法实现数据解析
Aug 25 #Python
Python+Opencv身份证号码区域提取及识别实现
Aug 25 #Python
Python Selenium实现无可视化界面过程解析
Aug 25 #Python
You might like
Jquery 快速构建可拖曳的购物车DragDrop
2009/11/30 Javascript
用js实现的模拟jquery的animate自定义动画(2.5K)
2010/07/20 Javascript
javascript中"/"运算符常见错误
2010/10/13 Javascript
JQuery操作三大控件(下拉,单选,复选)的方法
2013/08/06 Javascript
JS高级调试技巧:捕获和分析 JavaScript Error详解
2014/03/16 Javascript
用JavaScript实现一个代码简洁、逻辑不复杂的多级树
2014/05/23 Javascript
javascript中Array()数组函数详解
2015/08/23 Javascript
AngularJS表单验证中级篇(3)
2016/09/28 Javascript
JavaScript 闭包机制详解及实例代码
2016/10/10 Javascript
深入学习js瀑布流布局
2016/10/14 Javascript
深入浅析AngularJS中的一次性数据绑定 (bindonce)
2017/05/11 Javascript
使用重写url机制实现验证码换一张功能
2017/08/01 Javascript
Vue中的v-for循环key属性注意事项小结
2018/08/12 Javascript
详解angular2如何手动点击特定元素上的点击事件
2018/10/16 Javascript
js面向对象封装级联下拉菜单列表的实现步骤
2021/02/08 Javascript
Python爬取网易云音乐上评论火爆的歌曲
2017/01/19 Python
windows系统下Python环境的搭建(Aptana Studio)
2017/03/06 Python
Python 获取主机ip与hostname的方法
2018/12/17 Python
Python实现的拉格朗日插值法示例
2019/01/08 Python
pandas DataFrame 行列索引及值的获取的方法
2019/07/02 Python
Python 中的 global 标识对变量作用域的影响
2019/08/12 Python
多版本python的pip 升级后, pip2 pip3 与python版本失配解决方法
2019/09/11 Python
python selenium循环登陆网站的实现
2019/11/04 Python
基于Html5 canvas实现裁剪图片和马赛克功能及又拍云上传图片 功能
2019/07/09 HTML / CSS
W Concept美国:精选全球独立设计师
2017/02/22 全球购物
荷兰网上买鞋:MooieSchoenen.nl
2017/09/12 全球购物
迎八一活动主题
2014/01/31 职场文书
酒店总经理助理职责
2014/02/12 职场文书
献爱心大型公益活动策划方案
2014/09/15 职场文书
公司领导班子群众路线四风问题对照检查材料
2014/10/02 职场文书
给病人的慰问信
2015/03/23 职场文书
致创业您:正能量激励人心句子(48条)
2019/08/15 职场文书
基于Python和openCV实现图像的全景拼接详细步骤
2021/10/05 Python
利用Sharding-Jdbc进行分库分表的操作代码
2022/01/22 Java/Android
Python可视化学习之matplotlib内置单颜色
2022/02/24 Python
springboot+rabbitmq实现智能家居实例详解
2022/07/23 Java/Android