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 dict.get()和dict['key']的区别详解
Jun 30 Python
Python中几种导入模块的方式总结
Apr 27 Python
利用python将图片转换成excel文档格式
Dec 30 Python
Python+OpenCV让电脑帮你玩微信跳一跳
Jan 04 Python
Python创建普通菜单示例【基于win32ui模块】
May 09 Python
Python使用folium excel绘制point
Jan 03 Python
Python实现隐马尔可夫模型的前向后向算法的示例代码
Dec 31 Python
用pytorch的nn.Module构造简单全链接层实例
Jan 14 Python
Python图片处理模块PIL操作方法(pillow)
Apr 07 Python
python--shutil移动文件到另一个路径的操作
Jul 13 Python
python 对象真假值的实例(哪些视为False)
Dec 11 Python
pycharm 如何查看某一函数源码的快捷键
May 12 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
php 编写安全的代码时容易犯的错误小结
2010/05/20 PHP
PHP中“简单工厂模式”实例代码讲解
2012/09/04 PHP
PHP转盘抽奖接口实例
2015/02/09 PHP
PHP面向对象程序设计组合模式与装饰模式详解
2016/12/02 PHP
解决PHP 7编译安装错误:cannot stat ‘phar.phar’: No such file or directory
2017/02/25 PHP
Extjs 几个方法的讨论
2010/01/28 Javascript
jquery ajax提交表单数据的两种实现方法
2010/04/29 Javascript
兼容IE、FireFox、Chrome等浏览器的xml处理函数js代码
2011/11/30 Javascript
javascript特殊用法示例介绍
2013/11/29 Javascript
jQuery html()方法使用不了无法显示内容的问题
2014/08/06 Javascript
JS实现文字链接感应鼠标淡入淡出改变颜色的方法
2015/02/26 Javascript
JavaScript两个变量交换值的实现方法
2017/03/01 Javascript
vue-resource 拦截器(interceptor)的使用详解
2017/07/04 Javascript
完美解决手机网页中输入框被输入法遮挡的问题
2017/12/19 Javascript
从零开始搭建一个react项目开发
2018/02/09 Javascript
浅谈Webpack多页应用HMR卡住问题
2019/04/24 Javascript
bootstrap table实现横向合并与纵向合并
2019/07/18 Javascript
Vue Extends 扩展选项用法完整实例
2019/09/17 Javascript
vue实现文字加密功能
2019/09/27 Javascript
python开发之thread实现布朗运动的方法
2015/11/11 Python
Python中使用logging和traceback模块记录日志和跟踪异常
2019/04/09 Python
使用Python制作简单的小程序IP查看器功能
2019/04/16 Python
pyqt5之将textBrowser的内容写入txt文档的方法
2019/06/21 Python
利用Python产生加密表和解密表的实现方法
2019/10/15 Python
如何基于python操作excel并获取内容
2019/12/24 Python
python实现的分层随机抽样案例
2020/02/25 Python
PyCharm2019.3永久激活破解详细图文教程,亲测可用(不定期更新)
2020/10/29 Python
Python Django路径配置实现过程解析
2020/11/05 Python
HTML实现代码雨源码及效果示例
2020/02/25 HTML / CSS
JD Sports芬兰:英国领先的运动鞋和运动服饰零售商
2018/11/16 全球购物
2014大学生全国两会学习心得体会
2014/03/13 职场文书
2014年个人业务工作总结
2014/11/17 职场文书
2014年乡镇个人工作总结
2014/12/03 职场文书
吴仁宝观后感
2015/06/09 职场文书
学生会主席任命书
2015/09/21 职场文书
班主任培训研修日志
2015/11/13 职场文书