11.python并发入门(part4 死锁与递归锁)

一、关于死锁。

死锁,就是当多个进程或者线程在执行的过程中,因争夺共享资源而造成的一种互相等待的现象,一旦产生了死锁,不加人工处理,程序会一直等待下去,这也被称为死锁进程。

下面是一个产生“死锁”现象的例子:

import threading

import time

lock_a = threading.Lock()

lock_b = threading.Lock()

class test_thread(threading.Thread):

def __init__(self):

super(test_thread,self).__init__()

def run(self):

self.fun1()

self.fun2()

def fun1(self):

lock_a.acquire()

print "I am %s , get res: %s---%s" %(self.name, "ResA",time.time())

lock_b.acquire()

print "I am %s , get res: %s---%s" %(self.name, "ResB",time.time())

lock_b.release()

lock_a.release()

def fun2(self):

lock_b.acquire()

print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))

time.sleep(0.2)

lock_a.acquire()

print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))

lock_a.release()

lock_b.release()

if __name__ == "__main__":

print "start---------------------------%s"%(time.time())

for i in range(0, 10):

my_thread = test_thread()

my_thread.start()

输出执行结果:

start---------------------------1494682814.1

I am Thread-1 , get res: ResA---1494682814.1

I am Thread-1 , get res: ResB---1494682814.1

I am Thread-1 , get res: ResB---1494682814.1

I am Thread-2 , get res: ResA---1494682814.1

下面来分析代码,为什么会产生死锁:

开了10个线程,首先肯定会有一个线程去拿到lock_a这把锁,其余的线程只能阻塞,一直等到lock_a这把锁被释放,然后这个线程又获得了一把锁就是lock_b,执行完了一条print操作后,释放lock_b这把锁,此时,其他的线程还是没有办法去执行func1里面的资源,释放了lock_b这把锁后,紧接着lock_a也被释放了,此时,下一个线程就可以去执行func1中的资源了,接下来,线程1执行到了func2拿到了lock_b这把锁,然后执行一个print输出,I am Thread-1 , get res: ResB---1494682814.1,sleep0.2秒,在第一个线程sleep的过程中第二个线程开始执行,第二个线程在执行func1的时候,首先拿到了lock_a这把锁,执行了下面的print语句,I am Thread-2 , get res: ResA---1494682814.1,此时的情况就是线程1拿到了lock_b这把锁,线程2拿到了lock_a这把锁,那么问题来了,线程2如果想继续执行func1后面的内容,需要去获得lock_b这把锁,而此时lock_b锁已经被线程1拿走了(线程1执行到了func2拿走了lock_b锁,并且还没被释放~),线程1,sleep0.2秒后,需要继续执行func2中的内容,此时,需要拿到lock_a锁,(lock_a锁,被线程2执行func1的时候拿走了,并且没有被释放~),现在的情况就是线程1需要lock_a锁,但是lock_a在线程2手里,线程2需要lock_b锁,但是lock_b锁在线程1手里~双方都没有办法执行到后面的释放操作。

这也就相当于两个人要做交易,甲手里有苹果乙手里有菠萝,甲想吃乙手里的菠萝,乙想吃甲手里的苹果,但是谁都不愿意先把自己手里的东西先给对方~所以程序就会一直卡在这。

这就是死锁形成的原理。

二、递归锁。

解决死锁问题有一个特别有效的方法,就是递归锁.

递归锁与普通的互斥锁最大的不同就是,一个锁的对象内部,维护了一个计数器,这个计数器的初始值是0,当一个线程acquire一次这个锁时,内部计数器+1,但是,这把锁的计数器一旦大于0,其他的线程是无法拿到这把锁的,只有当前线程可以拿。

(当前线程acquire一次,计数器+1,release一次计数器-1,所以,当前的线程想要彻底释放掉递归锁,acquire多少次,就要release多少次!!!)

(这个计数器,就是递归锁中的count。)

还拿刚才的那段产生死锁的代码来举例:

import threading

import time

r_lock = threading.RLock() #RLock是用来产生递归锁的一个类,产生一个递归锁。

class test_thread(threading.Thread):

def __init__(self):

super(test_thread,self).__init__()

def run(self):

self.fun1()

self.fun2()

def fun1(self):

r_lock.acquire() #count+1

print "I am %s , get res: %s---%s" %(self.name, "ResA",time.time())

r_lock.acquire() #count再+1

print "I am %s , get res: %s---%s" %(self.name, "ResB",time.time())

r_lock.release() #count -1

r_lock.release() #count 再-1

def fun2(self):

r_lock.acquire()

print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))

time.sleep(0.2)

r_lock.acquire()

print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))

r_lock.release()

r_lock.release()

if __name__ == "__main__":

print "start---------------------------%s"%(time.time())

r_lock = threading.RLock()

for i in range(0, 10):

my_thread = test_thread()

my_thread.start()

输出结果:

start---------------------------1494687542.43

I am Thread-1 , get res: ResA---1494687542.43

I am Thread-1 , get res: ResB---1494687542.43

I am Thread-1 , get res: ResB---1494687542.43

I am Thread-1 , get res: ResA---1494687542.63

I am Thread-2 , get res: ResA---1494687542.63

I am Thread-2 , get res: ResB---1494687542.63

I am Thread-2 , get res: ResB---1494687542.63

I am Thread-2 , get res: ResA---1494687542.83

I am Thread-4 , get res: ResA---1494687542.83

I am Thread-4 , get res: ResB---1494687542.83

I am Thread-4 , get res: ResB---1494687542.83

I am Thread-4 , get res: ResA---1494687543.04

I am Thread-6 , get res: ResA---1494687543.04

I am Thread-6 , get res: ResB---1494687543.04

I am Thread-6 , get res: ResB---1494687543.04

I am Thread-6 , get res: ResA---1494687543.24

I am Thread-8 , get res: ResA---1494687543.24

I am Thread-8 , get res: ResB---1494687543.24

I am Thread-8 , get res: ResB---1494687543.24

I am Thread-8 , get res: ResA---1494687543.44

I am Thread-10 , get res: ResA---1494687543.44

I am Thread-10 , get res: ResB---1494687543.44

I am Thread-10 , get res: ResB---1494687543.44

I am Thread-10 , get res: ResA---1494687543.65

I am Thread-5 , get res: ResA---1494687543.65

I am Thread-5 , get res: ResB---1494687543.65

I am Thread-5 , get res: ResB---1494687543.65

I am Thread-5 , get res: ResA---1494687543.85

I am Thread-9 , get res: ResA---1494687543.85

I am Thread-9 , get res: ResB---1494687543.85

I am Thread-9 , get res: ResB---1494687543.85

I am Thread-9 , get res: ResA---1494687544.06

I am Thread-7 , get res: ResA---1494687544.06

I am Thread-7 , get res: ResB---1494687544.06

I am Thread-7 , get res: ResB---1494687544.06

I am Thread-7 , get res: ResA---1494687544.26

I am Thread-3 , get res: ResA---1494687544.26

I am Thread-3 , get res: ResB---1494687544.26

I am Thread-3 , get res: ResB---1494687544.26

I am Thread-3 , get res: ResA---1494687544.46

从上面的例子来看,死锁的问题被完美的解决掉了。

最后,总结下递归锁:

在python中,如果同一个线程需要多次去访问同一个共享资源,这个时候,就可以使用递归锁(RLock),递归锁的内部,维护了一个Lock对象和一个counter计数变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

所以说RLock可以完全代替Lock,能用递归锁尽量用递归锁!

时间: 2024-10-20 05:44:03

11.python并发入门(part4 死锁与递归锁)的相关文章

11.python并发入门(part7 线程队列)

一.为什么要用队列? 队列是一种数据结构,数据结构是一种存放数据的容器,和列表,元祖,字典一样,这些都属于数据结构. 队列可以做的事情,列表都可以做,但是为什么我们还要去使用队列呢? 这是因为在多线程的情况下,列表是一种不安全的数据结构. 为什么不安全?可以看下面这个例子: #开启两个线程,这两个线程并发从列表中移除一个元素. import threading import time l1 = [1,2,3,4,5] def pri(): while l1: a = l1[-1] print a

11.python并发入门(part10 多进程之间实现通信,以及进程之间的数据共享)

一.进程队列. 多个进程去操作一个队列中的数据,外观上看起来一个进程队列,只是一个队列而已,单实际上,你开了多少个进程,这些进程一旦去使用这个队列,那么这个队列就会被复制多少份. (队列=管道+锁) 这么做的主要原因就是,不同进程之间的数据是无法共享的. 下面是使用进程队列使多进程之间互相通信的示例: 下面这个例子,就是往进程队列里面put内容. #!/usr/local/bin/python2.7 # -*- coding:utf-8 -*- import multiprocessing de

11.python并发入门(part11 进程同步锁,以及进程池,以及callback的概念)

一.关于进程锁. 其实关于进程锁没啥好讲的了,作用跟线程的互斥锁(又叫全局锁也叫同步锁)作用几乎是一样的. 都是用来给公共资源上锁,进行数据保护的. 当一个进程想去操作一个公共资源,它就可以给公共资源进程"上锁"的操作,其他进程如果也想去访问或者操作这个公共资源,那么其他的进程只能阻塞,等待刚刚的进程把锁释放,下一个进程才可以对这个公共资源进行操作. 下面是个关于进程锁的使用示范: #!/usr/local/bin/python2.7 # -*- coding:utf-8 -*- im

11.python并发入门(part6 Semaphore信号量)

一.什么是信号量. 信号量也是一种锁. 信号量的主要用途是用来控制线程的并发量的,BoundedSemaphore或Semaphore管理一个内置的计数器,每调用一次acquire()方法时,计数器-1,每调用一次release()方法时,内部计数器+1. 不过需要注意的是,Semaphore内部的计数器不能小于0!当它内部的计数器等于0的时候,这个线程会被锁定,进入阻塞状态,直到其他线程去调用release方法. BoundedSemaphore与Semaphore的唯一区别在于前者将在调用r

11.python并发入门(part12 初识协程)

一.协程的简介. 协程,又被称为微线程,虽然是单进程,单线程,但是在某种情况下,在python中的协程执行效率会优于多线程. 这是因为协程之间的切换和线程的切换是完全不一样的!协程的切换是由程序自身控制的(程序的开发者使用yield去进行控制,协程和协程之间的切换是可控制的,想什么时候切换就什么时候切换). 当使用多线程时,开的线程越多,协程的优势就越明显. 协程的另一个优点,就是无需锁机制,因为协程只有一个进程,和线程,不存在多线程或者多进程之间访问公共资源的冲突,所以说,在协程中无需加锁,如

11.python并发入门(part2 threading模块的基本使用)

一.在使用python多线程之前,你需要知道的. python的多线程中,实现并发是没有问题的,但是!!是无法实现真正的并行的. 这是因为python内部有个GIL锁(全局解释器锁),这个锁限制了在同一时刻,同一个进程中,只能有一个线程被运行!!! 二.threading模块的基本使用方法. 可以使用它来创建线程.有两种方式来创建线程. 1.通过继承Thread类,重写它的run方法. 2.创建一个threading.Thread对象,在它的初始化函数__init__中将可调用对象作为参数传入.

11.python并发入门(part9 多线程模块multiprocessing基本用法)

一.回顾多继承的概念. 由于GIL(全局解释器锁)的存在,在python中无法实现真正的多线程(一个进程里的多个线程无法在cpu上并行执行),如果想充分的利用cpu的资源,在python中需要使用进程. 二.multiprocessing模块的简介. multiprocessing是python中用来管理多进程的包,与threading用法非常类似,它主要使用multiprocessing.Process对象来创建一个进程对象,该进程可以运行在python的函数中. 该Process(进程)对象

11.python并发入门(part1 初识进程与线程,并发,并行,同步,异步)

一.什么是进程? 在说什么是进程之前,需要先插入一个进程切换的概念! 进程,可以理解为一个正在运行的程序. 现在考虑一个场景,假如有两个程序A和B,程序A在执行到一半的过程中,需要读取大量的数据输入(I/O操作),而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源.你是不是已经想到在程序A读取数据的过程中,让程序B去执行,当程序A读取完数据之后,让程序B暂停.这当然没问题,但这里有一个关键词:切换. 既然是切换,那么这就涉及到了状态的保存,状态的恢复,加上程序A与

11.python并发入门(part15 关于I/O多路复用)

一.为什么要产生I/O多路复用? 两个主机之间通信,主机A和主机B都需要开启socket,主机A首先要等待客户端来进行连接,这是会发起一个recvfrom的系统调用,如果主机B一直没有去连接主机A,没有给主机A发送任何数据,进程就会被阻塞,无法去做其他的事情(默认的阻塞I/O模型),一直阻塞到主机B去连接主机A,主机A收到了这个连接. 其实这种模式在单服务器,单客户端(两台主机之间单独通信)没有什么问题,如果说是多并发场景呢?服务端阻塞与第一个客户端发来的socket对象中,另外一个客户端2要发