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

一、死锁

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

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中的死锁、可重入锁和互斥锁的更多相关文章

  1. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等

    Java 中15种锁的介绍 Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等,在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类 ...

  2. Java 种15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁等等…

    Java 中15种锁的介绍 1,在读很多并发文章中,会提及各种各样的锁,如公平锁,乐观锁,下面是对各种锁的总结归纳: 公平锁/非公平锁 可重入锁/不可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲 ...

  3. 【python】-- GIL锁、线程锁(互斥锁)、递归锁(RLock)

    GIL锁 计算机有4核,代表着同一时间,可以干4个任务.如果单核cpu的话,我启动10个线程,我看上去也是并发的,因为是执行了上下文的切换,让看上去是并发的.但是单核永远肯定时串行的,它肯定是串行的, ...

  4. 通俗的讲解Python中的__new__()方法

    2020-3-17更新本文,对本文中存争议的例子进行了更新! 曾经我幼稚的以为认识了python的__init__()方法就相当于认识了类构造器,结果,__new__()方法突然出现在我眼前,让我突然 ...

  5. Android 死锁和重入锁

    死锁的定义: 1.一般的死锁 一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态,这就是一般死锁的定义. package com.cxt.t ...

  6. 关于C#中Timer定时器的重入问题解决方法(也适用于多线程)

    项目中用到了定时器随着服务启动作定时任务,按指定的准点时间定时执行相关操作,但是在指定准点时间内我只想让它执行一次,要避免重入问题的发生. 首先简单介绍一下timer,这里所说的timer是指的Sys ...

  7. Python--同步锁(互斥锁)、死锁(状态)、递归锁、信号量、Event对象

    同步锁/互斥锁 (Lock) import time import threading def sub(): global num #在每个线程中都获取这个全局变量 #num-=1 temp=num ...

  8. python并发编程-进程理论-进程方法-守护进程-互斥锁-01

    操作系统发展史(主要的几个阶段) 初始系统 1946年第一台计算机诞生,采用手工操作的方式(用穿孔卡片操作) 同一个房间同一时刻只能运行一个程序,效率极低(操作一两个小时,CPU一两秒可能就运算完了) ...

  9. Java中线程同步锁和互斥锁有啥区别?看完你还是一脸懵逼?

    首先不要钻概念牛角尖,这样没意义. 也许java语法层面包装成了sycnchronized或者明确的XXXLock,但是底层都是一样的.无非就是哪种写起来方便而已. 锁就是锁而已,避免多个线程对同一个 ...

随机推荐

  1. Laravel5如何向闭合函数内传递参数 where function 传参

    如上,怎么将$title 像$query一样,在函数内部使用? $result = UserMenus::with(['menu'=>function($query){ $query->w ...

  2. js实现倒计时(分:秒)

    上代码: //倒计时start 需要传入的参数为秒数,此方法倒计时结束后会自动刷新页面 function resetTime(timetamp){ var timer=null; var t=time ...

  3. html-DOM了解

    什么是 HTML DOM? HTML DOM 是: HTML 的标准对象模型 HTML 的标准编程接口 W3C 标准 HTML DOM 定义了所有 HTML 元素的对象和属性,以及访问它们的方法. 换 ...

  4. 利用cmd运行java程序

    在运行以下程序时,要确保正确配置java的环境变量!!! 此处仅仅使用“记事本”来写java程序!!! 1. 新建一个记事本文件,命名为HelloWorld.java 这里需要注意的是,要确保关闭了隐 ...

  5. Luogu P3170 [CQOI2015]标识设计 状态压缩,轮廓线,插头DP,动态规划

    看到题目显然是插头\(dp\),但是\(n\)和\(m\)的范围似乎不是很小.我们先不考虑复杂度设一下状态试试: 一共有三个连通分量,我们按照\(1,2,3\)的顺序来表示一下.轮廓线上\(0\)代表 ...

  6. print的位置差异

    第一种,for循环和print是同级的,只有当for循环结束了才会print d=[]for i in range(1, 101): d.append(i)print d[6::7] 第二种,也就是p ...

  7. vue项目配置及项目初识

    目录 Vue项目环境搭建 Vue项目创建 重构项目依赖 1.需要转移的文件 2.重构依赖 pycharm配置并启动vue项目 vue项目目录结构分析 vue组件(.vue文件) 全局脚本文件main. ...

  8. 【NOIP2016提高A组五校联考1】道路规划

    题目 分析 我们考虑,当现在有一个合法的集合时,如何往里面增加一个点,使这个集合仍然合法. 假设现在有一个合法的集合, 那么当我们加入一个点,它的道路穿过来整个集合,那么 然后搞一遍最长下降子序列就可 ...

  9. 【ipc-mq】根据mq的key查看使用进程

    使用ipcs -q可以得到key与msqid的对应关系,从而找到msgid webadmin@172.172.179.3:/usr/local/webapps/test_ma[17:17:36]$ i ...

  10. 最全的WEB前端开发程序员学习清单

    史上最全的WEB前端开发程序员学习清单! 今天为什么要给大家分享这篇文章呢,我发现最近来学前端的特别多,群里面整天都有人问:前端好找工作吗?前端要怎么学啊?前端工资怎么样?前端XX,前端XXX,虽然我 ...