python多线程中的死锁与递归锁

Python中的多线程是共享所在进程的资源和内存地址的,因此当多个线程同时操作同一个数据的时候,就容易出错。如下

from threading import Thread,currentThread
import time
def task():
    global n
    tem = n
    time.sleep(0.01) # 在此处休息0.01秒,足够开启所有的线程,他们所获取到的n都是0,这就导致了最后输出的n为1
    tem = tem + 1
    n = tem
    print("%s :n = %d" % (currentThread().name, n))

if __name__ == '__main__':
    n = 0
    th_li = []
    for i in range(10):
        th = Thread(target=task)
        th.start()
        th_li.append(th)
    for i in th_li:
        i.join()  # 等待所有线程执行完毕    
    print(n)

最后输出n等于1。为了避免这种情况的发生,我们可以在进程start()后马上join()这样就能够将并发改为串行。还有一种方式就是加锁,和多进程中的互斥锁一样。

from threading import Thread,currentThread,Lock
import time

def task():
    metux.acquire()
    global n
    tem = n
    time.sleep(0.01)
    tem = tem + 1
    n = tem
    print("%s :n = %d" % (currentThread().name, n))
    metux.release()

if __name__ == '__main__':
    n = 0
    th_li = []
    metux = Lock()
    for i in range(10):
        th = Thread(target=task)
        th.start()
        th_li.append(th)
    for i in th_li:
        i.join()
    print(n)

我们可以通过加锁和解锁来将并发改为串行,保证了数据的安全。但如果我们加锁之后,忘记了解锁,这样程序就会卡死。为此Python提供了一种便捷的写法

def task():
    metux.acquire()
    global n
    tem = n
    time.sleep(0.01)
    tem = tem + 1
    n = tem
    print("%s :n = %d" % (currentThread().name, n))
    metux.release()

上面这段代码可以简写为

def task():
    with metux:
        global n
        tem = n
        time.sleep(0.01)
        tem = tem + 1
        n = tem
        print("%s :n = %d" % (currentThread().name, n))

这样就不用担心加锁和解锁的问题。加锁是为了将任务的某个部分由并发改为串行,从而保证数据的安全。我们有时候也需要给任务加多把锁。但多把锁也容易产生新的问题:死锁

from threading import Thread,Lock
import time

class MyThread(Thread):

    def th1(self):
        l1.acquire()
        print(self.name," 拿到了 LOKC-1")
        l2.acquire()
        print(self.name, " 拿到了 LOKC-2")
        l2.release()
        l1.release()

    def th2(self):
        l2.acquire()
        print(self.name," 拿到了 LOKC-2")
        time.sleep(1)
        l1.acquire()
        print(self.name, " 拿到了 LOKC-1")
        l1.release()
        l2.release()

    def run(self):
        self.th1()
        self.th2()

if __name__ == "__main__":
    l1 = Lock()
    l2 = Lock()
    for i in range(10):
        mt = MyThread()
        mt.start()

执行结果

Thread-1  拿到了 LOKC-1
Thread-1  拿到了 LOKC-2
Thread-1  拿到了 LOKC-2
Thread-2  拿到了 LOKC-1

这里Thread-1拿到了 LOKC-2,准备拿LOKC-1。这里Thread-2拿到了 LOKC-1,准备拿LOKC-2。Thread-1需要的LOKC-1在Thread-2手中还没有释放掉,Thread-2需要的LOKC-2在Thread-1手中还没有释放掉。2个线程都在等待对方释放自己所需要的锁。程序就在此处卡死了。这就是死锁现象。为了解决这个问题,Python中的递归锁(RLock)就产生。

RLock内部包含着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

l1 = l2 = RLock()

你可能感兴趣的:(Python基础,python,多线程)