且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-05-09 10:05:10

一、关于死锁。

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

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

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,能用递归锁尽量用递归锁!




      本文转自苏浩智 51CTO博客,原文链接:http://blog.51cto.com/suhaozhi/1925393,如需转载请自行联系原作者