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


Posted in Python onJune 14, 2016

死锁示例
搞多线程的经常会遇到死锁的问题,学习操作系统的时候会讲到死锁相关的东西,我们用Python直观的演示一下。
死锁的一个原因是互斥锁。假设银行系统中,用户a试图转账100块给用户b,与此同时用户b试图转账200块给用户a,则可能产生死锁。
2个线程互相等待对方的锁,互相占用着资源不释放。

#coding=utf-8 
import time 
import threading 
class Account: 
  def __init__(self, _id, balance, lock): 
    self.id = _id 
    self.balance = balance 
    self.lock = lock 
 
  def withdraw(self, amount): 
    self.balance -= amount 
 
  def deposit(self, amount): 
    self.balance += amount 
 
 
def transfer(_from, to, amount): 
  if _from.lock.acquire():#锁住自己的账户 
    _from.withdraw(amount) 
    time.sleep(1)#让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁 
    print 'wait for lock...' 
    if to.lock.acquire():#锁住对方的账户 
      to.deposit(amount) 
      to.lock.release() 
    _from.lock.release() 
  print 'finish...' 
 
a = Account('a',1000, threading.Lock()) 
b = Account('b',1000, threading.Lock()) 
threading.Thread(target = transfer, args = (a, b, 100)).start() 
threading.Thread(target = transfer, args = (b, a, 200)).start()

防止死锁的加锁机制
问题:
你正在写一个多线程程序,其中线程需要一次获取多个锁,此时如何避免死锁问题。
解决方案:
在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的。举个例子:一个线程获取了第一个锁,然后在获取第二个锁的 时候发生阻塞,那么这个线程就可能阻塞其他线程的执行,从而导致整个程序假死。 解决死锁问题的一种方案是为程序中的每一个锁分配一个唯一的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_inner
  self.run()
 File "/usr/local/lib/python3.3/threading.py", line 596, in run
  self._target(*self._args, **self._kwargs)
 File "deadlock.py", line 49, in thread_1
  with 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 acquire
  raise 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实现多线程下载文件的代码实例
Jun 01 Python
python实现给微信公众号发送消息的方法
Jun 30 Python
python中将一个全部为int的list 转化为str的list方法
Apr 09 Python
numpy 计算两个数组重复程度的方法
Nov 07 Python
python3+selenium实现126邮箱登陆并发送邮件功能
Jan 23 Python
Django中使用session保持用户登陆连接的例子
Aug 06 Python
python进阶之自定义可迭代的类
Aug 20 Python
python使用pip安装模块出现ReadTimeoutError: HTTPSConnectionPool的解决方法
Oct 04 Python
Python 使用元类type创建类对象常见应用详解
Oct 17 Python
Python3基本输入与输出操作实例分析
Feb 14 Python
keras使用Sequence类调用大规模数据集进行训练的实现
Jun 22 Python
python3.7.2 tkinter entry框限定输入数字的操作
May 22 Python
实例探究Python以并发方式编写高性能端口扫描器的方法
Jun 14 #Python
Python使用dis模块把Python反编译为字节码的用法详解
Jun 14 #Python
Python的Flask框架中使用Flask-Migrate扩展迁移数据库的教程
Jun 14 #Python
Python的Flask框架中使用Flask-SQLAlchemy管理数据库的教程
Jun 14 #Python
全面了解Python的getattr(),setattr(),delattr(),hasattr()
Jun 14 #Python
浅谈python中的getattr函数 hasattr函数
Jun 14 #Python
深入解析Python中的线程同步方法
Jun 14 #Python
You might like
Joomla语言翻译类Jtext用法分析
2016/05/05 PHP
laravel如何开启跨域功能示例详解
2017/08/31 PHP
php+ajax实现无刷新文件上传功能(ajaxuploadfile)
2018/02/11 PHP
laravel 去掉index.php伪静态的操作方法
2019/10/12 PHP
让焦点自动跳转
2006/07/01 Javascript
JavaScript中Array 对象相关的几个方法
2006/12/22 Javascript
JS location几个方法小姐
2008/07/09 Javascript
jquery.ui.draggable中文文档
2009/11/24 Javascript
javascript 解析url的search方法
2010/02/09 Javascript
关于jquery ajax 调用带参数的webservice返回XML数据一个小细节
2012/07/31 Javascript
jQuery获取浏览器中的分辨率实现代码
2013/04/23 Javascript
非html5实现js版弹球游戏示例代码
2013/09/22 Javascript
通过jquery-ui中的sortable来实现拖拽排序的简单实例
2016/05/24 Javascript
js实现键盘自动打字效果
2016/12/23 Javascript
js实现文字列表无缝滚动效果
2017/06/23 Javascript
ES6顶层对象、global对象实例分析
2019/06/14 Javascript
vue实现从外部修改组件内部的变量的值
2020/07/30 Javascript
vue 计算属性和侦听器的使用小结
2021/01/25 Vue.js
[01:58]2018DOTA2亚洲邀请赛趣味视频——交流
2018/04/03 DOTA
[45:52]完美世界DOTA2联赛PWL S3 Forest vs INK ICE 第二场 12.09
2020/12/12 DOTA
python海龟绘图实例教程
2014/07/24 Python
python里对list中的整数求平均并排序
2014/09/12 Python
Sanic框架异常处理与中间件操作实例分析
2018/07/16 Python
python队列Queue的详解
2019/05/10 Python
python的pstuil模块使用方法总结
2019/07/26 Python
Python处理session的方法整理
2019/08/29 Python
Python图像处理二值化方法实例汇总
2020/07/24 Python
python 使用OpenCV进行简单的人像分割与合成
2021/02/02 Python
python解决OpenCV在读取显示图片的时候闪退的问题
2021/02/23 Python
购买英国原创艺术:Art Gallery
2018/08/25 全球购物
工会主席岗位责任制
2014/02/11 职场文书
员工试用期转正自我评价
2015/03/10 职场文书
2019感恩宣传标语!
2019/07/05 职场文书
教你用Java Swing实现自助取款机系统
2021/06/11 Java/Android
Java日常练习题,每天进步一点点(38)
2021/07/26 Java/Android
MySQL慢查询中的commit慢和binlog中慢事务的区别
2022/06/16 MySQL