举例讲解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. 53道Java线程面试题

    53道Java线程面试题 下面是Java线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序 ...

  2. lilo - 安装引导装入程序

    总述 主要功能: ” /sbin/lilo” - 安装引导装入程序 辅助用途: ”/sbin/lilo –q” - 查询影射表 ”/sbin/lilo –R” - 设置下次启动的默认命令行 ”/sbi ...

  3. Linux课程学习 第四课

    学习必须如蜜蜂一样,采过许多花,这才能酿出蜜来 这月事比较多,每课的笔记都会慢慢补回来的,做事得有始有终 在网络上,人们越来越倾向于传输压缩格式的文件,原因是压缩文件体积小,在网速相同的情况下,传输时 ...

  4. Zen Cart 138 在PHP5.3环境下出现的Fatal error: Cannot redeclare date_diff()

    Zen Cart 138 在PHP5.3环境下出现的Fatal error: Cannot redeclare date_diff() in includes/functions/functions_ ...

  5. 【学习】025 RocketMQ

    RocketMQ概述 RocketMQ 是一款分布式.队列模型的消息中间件,具有以下特点: 能够保证严格的消息顺序 提供丰富的消息拉取模式 高效的订阅者水平扩展能力 实时的消息订阅机制 亿级消息堆积能 ...

  6. 洛谷P5055 可持久化文艺平衡树 (可持久化treap)

    题目链接 文艺平衡树的可持久化版,可以使用treap实现. 作为序列使用的treap相对splay的优点如下: 1.代码短 2.容易实现可持久化 3.边界处理方便(splay常常需要在左右两端加上保护 ...

  7. MongoDB实现增删查方法

    1.添加信息 public void addInfo(Infomation infomation) { try{ // TODO Auto-generated method stub //连接Mong ...

  8. Spring Data JPA基本了解

    前言 自 JPA 伴随 Java EE 5 发布以来,受到了各大厂商及开源社区的追捧,各种商用的和开源的 JPA 框架如雨后春笋般出现,为开发者提供了丰富的选择.它一改之前 EJB 2.x 中实体 B ...

  9. Vue优化首页加载速度 CDN引入

    https://blog.csdn.net/blueberry_liang/article/details/80134563

  10. iOS的UILabel设置多行显示

    label.lineBreakMode = NSLineBreakByWordWrapping; label.numberOfLines = ;