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读取csv文件示例(python操作csv)
Mar 11 Python
Python程序员鲜为人知但你应该知道的17个问题
Jun 04 Python
Python中利用函数装饰器实现备忘功能
Mar 30 Python
一文总结学习Python的14张思维导图
Oct 17 Python
Python简单计算文件MD5值的方法示例
Apr 11 Python
Python if语句知识点用法总结
Jun 10 Python
更新pip3与pyttsx3文字语音转换的实现方法
Aug 08 Python
Python容器使用的5个技巧和2个误区总结
Sep 26 Python
将python包发布到PyPI和制作whl文件方式
Dec 25 Python
Ubuntu权限不足无法创建文件夹解决方案
Nov 14 Python
python3中TQDM库安装及使用详解
Nov 18 Python
python学习之使用Matplotlib画实时的动态折线图的示例代码
Feb 25 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
这部好评如潮的动漫 知名梗频出 但是画风劝退很多人
2020/03/08 日漫
PHP中基本符号及使用方法
2010/03/23 PHP
教你识别简单的免查杀PHP后门
2015/09/13 PHP
万能的php分页类
2017/07/06 PHP
php+javascript实现的动态显示服务器运行程序进度条功能示例
2017/08/07 PHP
js 异步处理进度条
2010/04/01 Javascript
javascript 上下banner替换具体实现
2013/11/14 Javascript
JS合并数组的几种方法及优劣比较
2014/09/19 Javascript
JavaScript获取网页支持表单字符集的方法
2015/04/02 Javascript
深入浅析JavaScript中的constructor
2016/04/19 Javascript
JS针对Array的各种操作汇总
2016/11/29 Javascript
EditPlus中的正则表达式 实战(2)
2016/12/15 Javascript
bootstrap table实现单击单元格可编辑功能
2017/03/28 Javascript
js动态添加带圆圈序号列表的实例代码
2021/02/18 Javascript
Python编程中字符串和列表的基本知识讲解
2015/10/14 Python
初步剖析C语言编程中的结构体
2016/01/16 Python
Python用模块pytz来转换时区
2016/08/19 Python
Python3 加密(hashlib和hmac)模块的实现
2017/11/23 Python
python 使用re.search()筛选后 选取部分结果的方法
2018/11/28 Python
python之mock模块基本使用方法详解
2019/06/27 Python
python+Django+pycharm+mysql 搭建首个web项目详解
2019/11/29 Python
Pytoch之torchvision.transforms图像变换实例
2019/12/30 Python
Python netmiko模块的使用
2020/02/14 Python
python使用信号量动态更新配置文件的操作
2020/04/01 Python
python filecmp.dircmp实现递归比对两个目录的方法
2020/05/22 Python
Tensorflow中k.gradients()和tf.stop_gradient()用法说明
2020/06/10 Python
CSS3实现任意图片lowpoly动画效果实例
2017/05/11 HTML / CSS
.NET初级开发工程师面试题(包括Javascript)
2012/08/22 面试题
物流管理毕业生自荐信范文
2014/03/15 职场文书
教师个人读书活动总结
2014/07/08 职场文书
七夕活动策划方案
2014/08/16 职场文书
房产销售员2015年终工作总结
2015/10/22 职场文书
2016年春节慰问信息大全
2015/11/30 职场文书
高二数学教学反思
2016/02/18 职场文书
CocosCreator ScrollView优化系列之分帧加载
2021/04/14 Python
浏览器常用基本操作之python3+selenium4自动化测试(基础篇3)
2021/05/21 Python