举例讲解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. 牛客假日团队赛5 K 金币馅饼 (DP 基础题)

    链接:https://ac.nowcoder.com/acm/contest/984/K 来源:牛客网 金币馅饼 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言 ...

  2. Mac下安装nginx并配置SSL实现Https的访问

    一.nginx安装 ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/insta ...

  3. 清北学堂清华大学钟皓曦神仙讲课day3摘要

    ---恢复内容开始--- 今天全是DP awsl,真的好难 先从斐波那契开始: dp:满足有一个状态边界条件(f[0]=0,f[1]=1) 边界条件:不需要计算其他状态的值而可以直接得出的状态或者最底 ...

  4. 七、设备驱动中的阻塞与非阻塞 IO(一)

    7.1 阻塞与非阻塞 IO 阻塞操作是指在执行设备操作的时候,若不能获取资源,则挂起进程直到满足可操作的条件后再进行操作.被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足. 非 ...

  5. python类库32[多进程通信Queue+Pipe+Value+Array]

    多进程通信 queue和pipe的区别: pipe用来在两个进程间通信.queue用来在多个进程间实现通信. 此两种方法为所有系统多进程通信的基本方法,几乎所有的语言都支持此两种方法. 1)Queue ...

  6. day_04 基本数据类型的结构和使用方法

    1. 简述Python的五大数据类型的作用.定义方式.使用方法: 1). 数字类型: 整型(int): 表示年龄.号码.级别:变量名=值,变量名=int(值):加减乘除.逻辑判断: 浮点型(float ...

  7. Burpsuite查看和修改请求

    打开上传测试网页(此处是自己搭建的OWASP平台),这个网页只能上传图片格式的文件 上传一张图片: 查看上传图片: 创建一个test.text文件: 配置浏览器代理,IP:127.0.0.1,端口:8 ...

  8. DevOps之持续集成Jenkins+Gitlab

    一.什么是DevOps DevOps(英文Development(开发)和Operations(技术运营)的组合)是一组过程.方法与系统的统称,DevOps是一组最佳实践强调(开发.运维.测试)在应用 ...

  9. CSS设置背景色

    最好用background-color:rgba(37,77,113,1); 因为用opacity会让所有自标签都改变

  10. Python 爬虫十六式 - 第二式:urllib 与 urllib3

    Python请求标准库 urllib 与 urllib3 学习一时爽,一直学习一直爽!   大家好,我是 Connor,一个从无到有的技术小白.上一次我们说到了什么是HTTP协议,那么这一次我们就要动 ...