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

  1. #coding=utf-8
  2. import time
  3. import threading
  4. class Account:
  5. def __init__(self, _id, balance, lock):
  6. self.id = _id
  7. self.balance = balance
  8. self.lock = lock
  9.  
  10. def withdraw(self, amount):
  11. self.balance -= amount
  12.  
  13. def deposit(self, amount):
  14. self.balance += amount
  15.  
  16. def transfer(_from, to, amount):
  17. if _from.lock.acquire():#锁住自己的账户
  18. _from.withdraw(amount)
  19. time.sleep(1)#让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁
  20. print 'wait for lock...'
  21. if to.lock.acquire():#锁住对方的账户
  22. to.deposit(amount)
  23. to.lock.release()
  24. _from.lock.release()
  25. print 'finish...'
  26.  
  27. a = Account('a',1000, threading.Lock())
  28. b = Account('b',1000, threading.Lock())
  29. threading.Thread(target = transfer, args = (a, b, 100)).start()
  30. threading.Thread(target = transfer, args = (b, a, 200)).start()

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

  1. import threading
  2. from contextlib import contextmanager
  3.  
  4. # Thread-local state to stored information on locks already acquired
  5. _local = threading.local()
  6.  
  7. @contextmanager
  8. def acquire(*locks):
  9. # Sort locks by object identifier
  10. locks = sorted(locks, key=lambda x: id(x))
  11.  
  12. # Make sure lock order of previously acquired locks is not violated
  13. acquired = getattr(_local,'acquired',[])
  14. if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
  15. raise RuntimeError('Lock Order Violation')
  16.  
  17. # Acquire all of the locks
  18. acquired.extend(locks)
  19. _local.acquired = acquired
  20.  
  21. try:
  22. for lock in locks:
  23. lock.acquire()
  24. yield
  25. finally:
  26. # Release locks in reverse order of acquisition
  27. for lock in reversed(locks):
  28. lock.release()
  29. del acquired[-len(locks):]

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

  1. import threading
  2. x_lock = threading.Lock()
  3. y_lock = threading.Lock()
  4.  
  5. def thread_1():
  6. while True:
  7. with acquire(x_lock, y_lock):
  8. print('Thread-1')
  9.  
  10. def thread_2():
  11. while True:
  12. with acquire(y_lock, x_lock):
  13. print('Thread-2')
  14.  
  15. t1 = threading.Thread(target=thread_1)
  16. t1.daemon = True
  17. t1.start()
  18.  
  19. t2 = threading.Thread(target=thread_2)
  20. t2.daemon = True
  21. t2.start()

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

  1. import threading
  2. x_lock = threading.Lock()
  3. y_lock = threading.Lock()
  4.  
  5. def thread_1():
  6.  
  7. while True:
  8. with acquire(x_lock):
  9. with acquire(y_lock):
  10. print('Thread-1')
  11.  
  12. def thread_2():
  13. while True:
  14. with acquire(y_lock):
  15. with acquire(x_lock):
  16. print('Thread-2')
  17.  
  18. t1 = threading.Thread(target=thread_1)
  19. t1.daemon = True
  20. t1.start()
  21.  
  22. t2 = threading.Thread(target=thread_2)
  23. t2.daemon = True
  24. t2.start()

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

  1. Exception in thread Thread-1:
  2. Traceback (most recent call last):
  3. File "/usr/local/lib/python3.3/threading.py", line 639, in _bootstrap_inner
  4. self.run()
  5. File "/usr/local/lib/python3.3/threading.py", line 596, in run
  6. self._target(*self._args, **self._kwargs)
  7. File "deadlock.py", line 49, in thread_1
  8. with acquire(y_lock):
  9. File "/usr/local/lib/python3.3/contextlib.py", line 48, in __enter__
  10. return next(self.gen)
  11. File "deadlock.py", line 15, in acquire
  12. raise RuntimeError("Lock Order Violation")
  13. RuntimeError: Lock Order Violation
  14. >>>

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

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

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

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

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

  1. import threading
  2.  
  3. # The philosopher thread
  4. def philosopher(left, right):
  5. while True:
  6. with acquire(left,right):
  7. print(threading.currentThread(), 'eating')
  8.  
  9. # The chopsticks (represented by locks)
  10. NSTICKS = 5
  11. chopsticks = [threading.Lock() for n in range(NSTICKS)]
  12.  
  13. # Create all of the philosophers
  14. for n in range(NSTICKS):
  15. t = threading.Thread(target=philosopher,
  16. args=(chopsticks[n],chopsticks[(n+1) % NSTICKS]))
  17. t.start()

Python中死锁的形成示例及死锁情况的防止的更多相关文章

  1. python中hashlib模块用法示例

    python中hashlib模块用法示例 我们以前介绍过一篇Python加密的文章:Python 加密的实例详解.今天我们看看python中hashlib模块用法示例,具体如下. hashlib ha ...

  2. python中json的操作示例

    先上一段示例 # -*- coding: cp936 -*- import json #构造一个示例数据,并打印成易读样式 j = {} j["userName"]="a ...

  3. A Basic Example of Threads Synchronization in Python, python中的线程同步示例

    http://zulko.github.io/blog/2013/09/19/a-basic-example-of-threads-synchronization-in-python/ We will ...

  4. python杂谈:Python中\r的用法示例

    \r 默认表示将输出的内容返回到第一个指针,这样的话,后面的内容会覆盖前面的内容 import sys import time def view_bar(num,total): rate = floa ...

  5. Python中的多线程编程,线程安全与锁(一)

    1. 多线程编程与线程安全相关重要概念 在我的上篇博文 聊聊Python中的GIL 中,我们熟悉了几个特别重要的概念:GIL,线程,进程, 线程安全,原子操作. 以下是简单回顾,详细介绍请直接看聊聊P ...

  6. Python中变量的作用域(variable scope)

    http://www.crifan.com/summary_python_variable_effective_scope/ 解释python中变量的作用域 示例: 1.代码版 #!/usr/bin/ ...

  7. python中定义函数和参数的传递问题

    作者:達聞西链接:https://zhuanlan.zhihu.com/p/24162430来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 5.2.4 函数.生成器和类 ...

  8. python中赋值和浅拷贝与深拷贝

    初学编程的小伙伴都会对于深浅拷贝的用法有些疑问,今天我们就结合python变量存储的特性从内存的角度来谈一谈赋值和深浅拷贝~~~ 预备知识一——python的变量及其存储 在详细的了解python中赋 ...

  9. 举例讲解Python中的死锁、可重入锁和互斥锁

    举例讲解Python中的死锁.可重入锁和互斥锁 一.死锁 简单来说,死锁是一个资源被多次调用,而多次调用方都未能释放该资源就会造成死锁,这里结合例子说明下两种常见的死锁情况. 1.迭代死锁 该情况是一 ...

随机推荐

  1. angularJS和requireJS和angularAMD

    最近因为要用到angularJS开发项目,因为涉及到的静态资源比较多,所以想把js文件通过requireJS来按需加载,这两个框架以前都使用过,但是结合到一起还没有用过,那就试一下,看能否达到目的. ...

  2. 3-Ubuntu下python3安装pymysql模块(1)

    命令:sudo pip3 install pymysql

  3. Spring MVC @RequestParam(5)

    案例来说明 1 @RequestMapping("user/add") 2 public String add(@RequestParam("name") St ...

  4. tp5.0x代码执行

    1.拿到站首先平复一下心情 看了一下robots.txt结构像dedecms,网站还存在CDN,日了狗看到这里其实都想放弃来着,懒癌晚期,然后接着使用云悉平台走了一波,看了一下得到真实IP,看来只给w ...

  5. EXCEL表格链接SQLSEVER数据库

    Sub 数据库连接()     Set Cnn = CreateObject("ADODB.Connection")    Set rs = CreateObject(" ...

  6. sql(7)

    EXCEPT是指在第一个集合中存在,但是不存在于第二个集合中的数据. EXCEPT 子句/运算符用于将两个 SELECT 语句结合在一起,并返回第一个 SELECT 语句的结果中那些不存在于第二个 S ...

  7. Buy Low, Buy Lower

    Buy Low, Buy Lower 给出一个长度为N序列\(\{a_i\}\),询问最长的严格下降子序列,以及这样的序列的个数,\(1 <= N <= 5000\). 解 显然我们可以很 ...

  8. [JZOJ3348] 【NOI2013模拟】秘密任务

    题目 题目大意 给你一个无向图,你要割掉一些边使得\(1\)到\(n\)的所有最短路径被阻截. 割掉一个边\((u,v)\)的代价为\(a_u\)或\(a_v\)(记为两种不同的方案). 问最小代价及 ...

  9. JavaScript工作原理

    HTML代码所表示的文档是一种静态文档,几乎没有交互功能,很难使页面成为动态页面.增加脚本语言,可使数据发送到服务器之前先进行处理和校验,动态地创建新的Web内容,更重要的是,引入脚本语言使我们有了事 ...

  10. BZOJ 2957楼房重建

    传送门 线段树 //Twenty #include<cstdio> #include<cstdlib> #include<iostream> #include< ...