更多编程教程请到:菜鸟教程 https://www.piaodoo.com/

友情链接:好看站 http://www.nrso.net/

问题

你正在写一个多线程程序,其中线程需要一次获取多个锁,此时如何避免死锁问题。

解决方案

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

import threading
from contextlib import contextmanager

Thread-local state to stored information on locks already acquired

_local = threading.local()

@contextmanager
def acquire(*locks):

Sort locks by object identifier

locks = sorted(locks, key=lambda x: id(x))

Make sure lock order of previously acquired locks is not violated

acquired = getattr(_local,‘acquired’,[])
if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
raise RuntimeError(‘Lock Order Violation’)

Acquire all of the locks

acquired.extend(locks)
_local.acquired = acquired

try:
for lock in locks:
lock.acquire()
yield
finally:
# Release locks in reverse order of acquisition
for lock in reversed(locks):
lock.release()
del acquired[-len(locks):]

如何使用这个上下文管理器呢?你可以按照正常途径创建一个锁对象,但不论是单个锁还是多个锁中都使用 acquire() 函数来申请锁, 示例如下:

import threading
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.daemon = True
t1.start()

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

如果你执行这段代码,你会发现它即使在不同的函数中以不同的顺序获取锁也没有发生死锁。 其关键在于,在第一段代码中,我们对这些锁进行了排序。通过排序,使得不管用户以什么样的顺序来请求锁,这些锁都会按照固定的顺序被获取。 如果有多个 acquire() 操作被嵌套调用,可以通过线程本地存储(TLS)来检测潜在的死锁问题。 假设你的代码是这样写的:

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

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-2’)

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

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

如果你运行这个版本的代码,必定会有一个线程发生崩溃,异常信息可能像这样:

Exception in thread Thread-1:
Traceback (most recent call last):File "/usr/local/lib/python3.3/threading.py", line 639, in _bootstrap_innerself.run()File "/usr/local/lib/python3.3/threading.py", line 596, in runself._target(*self._args, **self._kwargs)File "deadlock.py", line 49, in thread_1with acquire(y_lock):File "/usr/local/lib/python3.3/contextlib.py", line 48, in __enter__return next(self.gen)File "deadlock.py", line 15, in acquireraise RuntimeError("Lock Order Violation")
RuntimeError: Lock Order Violation
>>>

发生崩溃的原因在于,每个线程都记录着自己已经获取到的锁。 acquire() 函数会检查之前已经获取的锁列表, 由于锁是按照升序排列获取的,所以函数会认为之前已获取的锁的id必定小于新申请到的锁,这时就会触发异常。

讨论

死锁是每一个多线程程序都会面临的一个问题(就像它是每一本操作系统课本的共同话题一样)。根据经验来讲,尽可能保证每一个 线程只能同时保持一个锁,这样程序就不会被死锁问题所困扰。一旦有线程同时申请多个锁,一切就不可预料了。

死锁的检测与恢复是一个几乎没有优雅的解决方案的扩展话题。一个比较常用的死锁检测与恢复的方案是引入看门狗计数器。当线程正常 运行的时候会每隔一段时间重置计数器,在没有发生死锁的情况下,一切都正常进行。一旦发生死锁,由于无法重置计数器导致定时器 超时,这时程序会通过重启自身恢复到正常状态。

避免死锁是另外一种解决死锁问题的方式,在进程获取锁的时候会严格按照对象id升序排列获取,经过数学证明,这样保证程序不会进入 死锁状态。证明就留给读者作为练习了。避免死锁的主要思想是,单纯地按照对象id递增的顺序加锁不会产生循环依赖,而循环依赖是 死锁的一个必要条件,从而避免程序进入死锁状态。

下面以一个关于线程死锁的经典问题:“哲学家就餐问题”,作为本节最后一个例子。题目是这样的:五位哲学家围坐在一张桌子前,每个人 面前有一碗饭和一只筷子。在这里每个哲学家可以看做是一个独立的线程,而每只筷子可以看做是一个锁。每个哲学家可以处在静坐、 思考、吃饭三种状态中的一个。需要注意的是,每个哲学家吃饭是需要两只筷子的,这样问题就来了:如果每个哲学家都拿起自己左边的筷子, 那么他们五个都只能拿着一只筷子坐在那儿,直到饿死。此时他们就进入了死锁状态。 下面是一个简单的使用死锁避免机制解决“哲学家就餐问题”的实现:

import threading

The philosopher thread

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

The chopsticks (represented by locks)

NSTICKS = 5
chopsticks = [threading.Lock() for n in range(NSTICKS)]

Create all of the philosophers

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

最后,要特别注意到,为了避免死锁,所有的加锁操作必须使用 acquire() 函数。如果代码中的某部分绕过acquire 函数直接申请锁,那么整个死锁避免机制就不起作用了。

以上就是Python 防止死锁的方法的详细内容,更多关于Python 防止死锁的资料请关注菜鸟教程www.piaodoo.com其它相关文章!

Python 防止死锁的方法相关推荐

  1. python queue死锁_Python学习【第24篇】:死锁,递归锁,信号量,Event事件,线程Queue...

    一.死锁现象与递归锁 进程也是有死锁的 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去.此时称系统处于死锁状态或系统 ...

  2. Python 字典(Dictionary) get()方法

    Python 字典(Dictionary) get()方法 描述 Python 字典(Dictionary) get() 函数返回指定键的值,如果值不在字典中返回默认值. 语法 get()方法语法: ...

  3. Python最大堆排序实现方法

    Python最大堆排序实现方法,具体代码如下:   # -*- coding: utf-8 -*-   def merge_sort(seq, cmp=cmp, sentinel=None):     ...

  4. 刻意练习:Python基础 -- Task11. 魔法方法

    背景 我们准备利用17天时间,将 "Python基础的刻意练习" 分为如下任务: Task01:变量.运算符与数据类型(1day) Task02:条件与循环(1day) Task0 ...

  5. python操作yaml的方法详解

    这篇文章主要为大家介绍了python操作yaml的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助 一.参考链接 https://pyyaml.org/wiki/PyYAM ...

  6. python中函数和方法的区别?Python编程判断当前获取的对象是函数还是方法

    python中函数和方法的区别?Python编程判断当前获取的对象是函数还是方法 目录

  7. python集合(set)+常见方法+交叉并补

    python集合(set)+常见方法+交叉并补 集合的定义 定义:由不同元素组成的集合,集合是一组无序排列 可hash值,可作为字典的key. 特性:集合的目的是将不同的值存放在一起,不同的集合间用来 ...

  8. python print 的使用方法

    python print 的使用方法 视频 https://www.bilibili.com/video/BV1mK4y187zN?from=search&seid=3961255759492 ...

  9. python加号换行,Python字符串拼接六种方法介绍

    Python字符串拼接的6种方法: 1.加号 第一种,有编程经验的人,估计都知道很多语言里面是用加号连接两个字符串,Python里面也是如此直接用"+"来连接两个字符串: prin ...

最新文章

  1. 如何规划网站设计方案让用户访问更加舒适?
  2. mysql 安装 安全错误_MySQL的安全问题从安装开始说起
  3. java中 int 比较_java中Integer与int的种种比较你知道多少?
  4. Oracle-Decode()函数和CASE语句的不同
  5. [图形]图形API的两种模式
  6. 中3d库后接负载_500W电源横评:交叉负载放倒3款产品
  7. ubuntu环境搭建六:安装chrome浏览器
  8. Tensorflow高级封装
  9. iOS 程序上传流程
  10. spark将rdd转为string_SparkCore---RDD依赖
  11. c++ stl下的sort()函数介绍及基本用法
  12. 简述神经元网络控制的作用和特点
  13. ADB的使用-简单命令
  14. 00_51入门知识积累__常用工具(二)
  15. DBCS(Double-Byte Character Set, 双位元组字元集)
  16. 机械革命 无界16pro闪屏
  17. KVM虚拟化平台搭建、工作模式与原理
  18. Zero Forex领汇来告诉你什么是WH?
  19. Spring - Spring容器概念及其初始化过程
  20. 图像轮廓生成Freeman码

热门文章

  1. 图片base64编码的前端展示及后端解码,编码
  2. 云计算机ppt模板,云计算的PPT模板
  3. 跑步用挂脖耳机好还是无线耳机、公认最好的跑步耳机推荐
  4. 2021-08-07 解决安装MacTeX后界面显示法语问题
  5. 人工智能如何改变了我们的日常生活?
  6. nssl 1438.战略威慑
  7. 弘辽科技:拼多多推广单元和推广计划是一样的吗?
  8. 微信公众号页面中监听手机“返回”,并回到公众号里
  9. ElasticSearch中的分片(shards)是什么
  10. 网站seo优化3-7天快速上百度首页靠谱吗?怎么做