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中bisect模块用法实例
Sep 25 Python
python函数形参用法实例分析
Aug 04 Python
对Python的Django框架中的项目进行单元测试的方法
Apr 11 Python
Python实现优先级队列结构的方法详解
Jun 02 Python
Python中json格式数据的编码与解码方法详解
Jul 01 Python
python爬取各类文档方法归类汇总
Mar 22 Python
Python实现的knn算法示例
Jun 14 Python
深入解析Python小白学习【操作列表】
Mar 23 Python
python实现图片九宫格分割
Mar 07 Python
在pycharm中配置Anaconda以及pip源配置详解
Sep 09 Python
Python selenium 加载并保存QQ群成员,去除其群主、管理员信息的示例代码
May 28 Python
python爬虫基础知识点整理
Jun 02 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
全国FM电台频率大全 - 14 江西省
2020/03/11 无线电
php自动识别文件编码并转换为UTF-8的方法
2014/06/12 PHP
PHP判断文章里是否有图片的简单方法
2014/07/26 PHP
php中header设置常见文件类型的content-type
2015/06/23 PHP
PHP Ajax实现无刷新附件上传
2016/08/17 PHP
php基于闭包实现函数的自调用(递归)实例分析
2016/11/11 PHP
gearman中任务的优先级和返回状态实例分析
2020/02/27 PHP
javascript 火狐(firefox)不显示本地图片问题解决
2008/07/05 Javascript
多种方式实现JS调用后台方法进行数据交互
2013/08/20 Javascript
location.href用法总结(最主要的)
2013/12/27 Javascript
详解JavaScript正则表达式中的global属性的使用
2015/06/16 Javascript
jQuery实现鼠标悬停背景翻转的黑色导航菜单代码
2015/09/14 Javascript
JS实现具备延时功能的滑动门菜单效果
2015/09/17 Javascript
js+canvas绘制五角星的方法
2016/01/28 Javascript
有关suggest快速删除后仍然出现下拉列表的bug问题
2016/12/02 Javascript
详解js的六大数据类型
2016/12/27 Javascript
微信小程序 picker 组件详解及简单实例
2017/01/10 Javascript
javascript 中Cookie读、写与删除操作
2017/03/29 Javascript
微信小程序自定义导航教程(兼容各种手机)
2018/12/12 Javascript
[03:00]《DAC最前线》之欧美新秀VS老将
2015/02/01 DOTA
浅谈Python数据类型之间的转换
2016/06/08 Python
python之文件的读写和文件目录以及文件夹的操作实现代码
2016/08/28 Python
python通过pip更新所有已安装的包实现方法
2017/05/19 Python
django模型层(model)进行建表、查询与删除的基础教程
2017/11/21 Python
Python求解任意闭区间的所有素数
2018/06/10 Python
python递归下载文件夹下所有文件
2019/08/31 Python
python绘制随机网络图形示例
2019/11/21 Python
Python colormap库的安装和使用详情
2020/10/06 Python
Python存储读取HDF5文件代码解析
2020/11/25 Python
银行求职信个人范文
2013/12/16 职场文书
班级德育工作实施方案
2014/02/21 职场文书
幼儿教师师德师风演讲稿
2014/08/22 职场文书
2014年作风建设工作总结
2014/10/29 职场文书
党的群众路线教育实践活动制度建设计划方案
2014/10/31 职场文书
mysql字符串截取函数小结
2021/04/05 MySQL
python可视化大屏库big_screen示例详解
2021/11/23 Python