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


Posted in Python onNovember 05, 2015

一、死锁

简单来说,死锁是一个资源被多次调用,而多次调用方都未能释放该资源就会造成死锁,这里结合例子说明下两种常见的死锁情况。

1、迭代死锁

该情况是一个线程“迭代”请求同一个资源,直接就会造成死锁:

import threading
import time
class MyThread(threading.Thread):
  def run(self):
    global num
    time.sleep(1)
    if mutex.acquire(1):
      num = num+1
      msg = self.name+' set num to '+str(num)
      print msg
      mutex.acquire()
      mutex.release()
      mutex.release()
num = 0
mutex = threading.Lock()
def test():
  for i in range(5):
    t = MyThread()
    t.start()
if __name__ == '__main__':
  test()

上例中,在run函数的if判断中第一次请求资源,请求后还未 release ,再次acquire,最终无法释放,造成死锁。这里例子中通过将print下面的两行注释掉就可以正常执行了 ,除此之外也可以通过可重入锁解决,后面会提到。

2、互相调用死锁

上例中的死锁是在同一个def函数内多次调用造成的,另一种情况是两个函数中都会调用相同的资源,互相等待对方结束的情况。如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

import threading
import time
class MyThread(threading.Thread):
  def do1(self):
    global resA, resB
    if mutexA.acquire():
       msg = self.name+' got resA'
       print msg
       if mutexB.acquire(1):
         msg = self.name+' got resB'
         print msg
         mutexB.release()
       mutexA.release()
  def do2(self):
    global resA, resB
    if mutexB.acquire():
       msg = self.name+' got resB'
       print msg
       if mutexA.acquire(1):
         msg = self.name+' got resA'
         print msg
         mutexA.release()
       mutexB.release()
  def run(self):
    self.do1()
    self.do2()
resA = 0
resB = 0
mutexA = threading.Lock()
mutexB = threading.Lock()
def test():
  for i in range(5):
    t = MyThread()
    t.start()
if __name__ == '__main__':
  test()

这个死锁的示例稍微有点复杂。具体可以理下。

二、可重入锁

为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。这里以例1为例,如果使用RLock代替Lock,则不会发生死锁:

import threading
import time
class MyThread(threading.Thread):
  def run(self):
    global num
    time.sleep(1)
    if mutex.acquire(1):
      num = num+1
      msg = self.name+' set num to '+str(num)
      print msg
      mutex.acquire()
      mutex.release()
      mutex.release()
num = 0
mutex = threading.RLock()
def test():
  for i in range(5):
    t = MyThread()
    t.start()
if __name__ == '__main__':
  test()

和上面那个例子的不同之处在于threading.Lock()换成了threading.RLock() 。

三、互斥锁
python threading模块有两类锁:互斥锁(threading.Lock )和可重用锁(threading.RLock)。两者的用法基本相同,具体如下:

lock = threading.Lock()
lock.acquire()
dosomething……
lock.release()

RLock的用法是将threading.Lock()修改为threading.RLock()。便于理解,先来段代码:

[root@361way lock]# cat lock1.py
#!/usr/bin/env python
# coding=utf-8
import threading              # 导入threading模块
import time               # 导入time模块
class mythread(threading.Thread):    # 通过继承创建类
  def __init__(self,threadname):   # 初始化方法
    # 调用父类的初始化方法
    threading.Thread.__init__(self,name = threadname)
  def run(self):             # 重载run方法
    global x         # 使用global表明x为全局变量
    for i in range(3):
      x = x + 1
    time.sleep(5)     # 调用sleep函数,让线程休眠5秒
    print x
tl = []               # 定义列表
for i in range(10):
  t = mythread(str(i))        # 类实例化
  tl.append(t)           # 将类对象添加到列表中
x=0                 # 将x赋值为0
for i in tl:
  i.start()

这里执行的结果和想想的不同,结果如下:

[root@361way lock]# python lock1.py
30
30
30
30
30
30
30
30
30
30

为什么结果都是30呢?关键在于global 行和 time.sleep行。

1、由于x是一个全局变量,所以每次循环后 x 的值都是执行后的结果值;

2、由于该代码是多线程的操作,所以在sleep 等待的时候,之前已经执行完成的线程会在这等待,而后续的进程在等待的5秒这段时间也执行完成 ,等待print。同样由于global 的原理,x被重新斌值。所以打印出的结果全是30 ;

3、便于理解,可以尝试将sleep等注释,你再看下结果,就会发现有不同。

在实际应用中,如抓取程序等,也会出现类似于sleep等待的情况。在前后调用有顺序或打印有输出的时候,就会现并发竞争,造成结果或输出紊乱。这里就引入了锁的概念,上面的代码修改下,如下:

[root@361way lock]# cat lock2.py
#!/usr/bin/env python
# coding=utf-8
import threading              # 导入threading模块
import time               # 导入time模块
class mythread(threading.Thread):          # 通过继承创建类
  def __init__(self,threadname):         # 初始化方法
    threading.Thread.__init__(self,name = threadname)
  def run(self):             # 重载run方法
    global x            # 使用global表明x为全局变量
    lock.acquire()           # 调用lock的acquire方法
    for i in range(3):
      x = x + 1
    time.sleep(5)      # 调用sleep函数,让线程休眠5秒
    print x
    lock.release()        # 调用lock的release方法
lock = threading.Lock()        # 类实例化
tl = []             # 定义列表
for i in range(10):
  t = mythread(str(i))      # 类实例化
  tl.append(t)       # 将类对象添加到列表中
x=0            # 将x赋值为0
for i in tl:
  i.start()           # 依次运行线程

执行的结果如下:

[root@361way lock]# python lock2.py
3
6
9
12
15
18
21
24
27
30

加锁的结果会造成阻塞,而且会造成开锁大。会根据顺序由并发的多线程按顺序输出,如果后面的线程执行过快,需要等待前面的进程结束后其才能结束 --- 写的貌似有点像队列的概念了 ,不过在加锁的很多场景下确实可以通过队列去解决。

Python 相关文章推荐
跟老齐学Python之for循环语句
Oct 02 Python
Django接受前端数据的几种方法总结
Nov 04 Python
Python实现输出程序执行进度百分比的方法
Sep 16 Python
Python cookbook(字符串与文本)在字符串的开头或结尾处进行文本匹配操作
Apr 20 Python
python 编写简单网页服务器的实例
Jun 01 Python
python django框架中使用FastDFS分布式文件系统的安装方法
Jun 10 Python
python随机生成库faker库api实例详解
Nov 28 Python
Numpy之reshape()使用详解
Dec 26 Python
Windows下Pycharm远程连接虚拟机中Centos下的Python环境(图文教程详解)
Mar 19 Python
Python调用SMTP服务自动发送Email的实现步骤
Feb 07 Python
用Python提取PDF表格的方法
Apr 11 Python
Python中的套接字编程是什么?
Jun 21 Python
用Python写飞机大战游戏之pygame入门(4):获取鼠标的位置及运动
Nov 05 #Python
python实现将内容分行输出
Nov 05 #Python
Python IDE PyCharm的基本快捷键和配置简介
Nov 04 #Python
Python实现周期性抓取网页内容的方法
Nov 04 #Python
Python压缩解压缩zip文件及破解zip文件密码的方法
Nov 04 #Python
python通过文件头判断文件类型
Oct 30 #Python
python制作花瓣网美女图片爬虫
Oct 28 #Python
You might like
PHP中使用unset销毁变量并内存释放问题
2012/07/05 PHP
基于PHP编程注意事项的小结
2013/04/27 PHP
强烈声明: 不要使用(include/require)_once
2013/06/06 PHP
关于php循环跳出的问题
2013/07/01 PHP
PHP中的socket_read和socket_recv区别详解
2015/02/09 PHP
CodeIgniter基于Email类发邮件的方法
2016/03/29 PHP
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
2010/06/18 Javascript
javascript 单例/单体模式(Singleton)
2011/04/07 Javascript
jQuery右键菜单contextMenu使用实例
2011/09/28 Javascript
JS的replace方法详细介绍
2012/11/09 Javascript
早该知道的7个JavaScript技巧
2013/03/27 Javascript
js使用eval解析json实例与注意事项分享
2014/01/18 Javascript
JavaScript弹出窗口方法汇总
2014/08/12 Javascript
JS数组操作(数组增加、删除、翻转、转字符串、取索引、截取(切片)slice、剪接splice、数组合并)
2016/05/20 Javascript
封装获取dom元素的简单实例
2016/07/08 Javascript
Javascript 两种刷新方法以及区别和适用范围
2017/01/17 Javascript
从零开始学习Node.js系列教程四:多页面实现的数学运算示例
2017/04/13 Javascript
vue.js单文件组件中非父子组件的传值实例
2018/09/13 Javascript
JavaScript实现京东快递单号查询
2020/11/30 Javascript
[33:17]OG vs VGJ.T 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
[01:16:16]DOTA2-DPC中国联赛定级赛 RNG vs Phoenix BO3第二场 1月8日
2021/03/11 DOTA
opencv 实现特定颜色线条提取与定位操作
2020/06/02 Python
常用的10个Python实用小技巧
2020/08/10 Python
Django执行源生mysql语句实现过程解析
2020/11/12 Python
解决python3.6用cx_Oracle库连接Oracle的问题
2020/12/07 Python
CSS实现雨滴动画效果的实例代码
2019/10/08 HTML / CSS
Hotter Shoes英国官网:英伦风格,舒适的鞋子
2017/12/28 全球购物
Dr. Martens马汀博士澳大利亚官网:马丁靴鼻祖
2019/07/02 全球购物
最新大学生自我评价
2013/09/24 职场文书
智能电子应届生求职信
2013/11/10 职场文书
入党积极分子自我鉴定
2014/02/18 职场文书
学习十八大演讲稿
2014/09/15 职场文书
农村党支部书记司法四风问题对照检查材料
2014/09/26 职场文书
邮政营业员岗位职责
2015/04/14 职场文书
逃出克隆岛观后感
2015/06/09 职场文书
初中数学课堂教学反思
2016/02/17 职场文书