互斥锁、死锁和递归锁

稀土 2017-11-29

一、互斥锁(Mutex)

在上节最后我们讲到了线程安全,线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

threading模块中定义了Lock类,可以方便的处理锁定:

#创建锁
lock = threading.Lock()
#锁定
lock.acquire(blocking=True, timeout=-1) 
#释放 
lock.release() 

还是以上节实例为例:

# -*- coding: UTF-8 -*-

import threading


class MyThread(threading.Thread):

    def run(self):
        global n
        lock.acquire()
        n += 1
        lock.release()
        print(self.name + ' set n to ' + str(n))

n = 0
lock = threading.Lock()

if __name__ == '__main__':

    for i in range(5):
        t = MyThread()
        t.start()

    print('final num: %d' % n)

输出:

Thread-1 set n to 1
Thread-2 set n to 2
Thread-3 set n to 3
Thread-4 set n to 4
Thread-5 set n to 5
final num: 5

加锁之后,我们可以确保数据的正确性

二、死锁

死锁是指一个资源被多次调用,而多次调用方都未能释放该资源就会造成一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

2.1 一个线程内部多次加锁却没有释放

import threading


class MyThread(threading.Thread):

    def run(self):
        global n1, n2
        lock.acquire()   # 加锁
        n1 += 1
        print(self.name + ' set n1 to ' + str(n1))
        lock.acquire()   # 再次加锁
        n2 += n1
        print(self.name + ' set n2 to ' + str(n2))
        lock.release()
        lock.release()

n1, n2 = 0, 0
lock = threading.Lock()

if __name__ == '__main__':
    thread_list = []
    for i in range(5):
        t = MyThread()
        t.start()
        thread_list.append(t)
    for t in thread_list:
        t.join()
    print('final num:%d ,%d' % (n1, n2))

结果:

Thread-1 set n1 to 1

# 会一直等待

2.2 多个程序间相互调用引起死锁

import threading,time
 
def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num +=1
    lock.release()
    return num
def run2():
    print("grab the second part data")
    lock.acquire()
    global  num2
    num2+=1
    lock.release()
    return num2
def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res,res2)
 
 
if __name__ == '__main__':
 
    num,num2 = 0,0
    lock = threading.Lock()
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()
 
while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('----all threads done---')
    print(num,num2) 

三、递归锁

上述两个问题都可以使用python的递归锁解决

# 递归锁
rlock = threading.RLOCK()

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

# -*- coding: UTF-8 -*-
import threading


class MyThread(threading.Thread):

    def run(self):
        global n1, n2
        lock.acquire()   # 加锁
        n1 += 1
        print(self.name + ' set n1 to ' + str(n1))
        lock.acquire()   # 再次加锁
        n2 += n1
        print(self.name + ' set n2 to ' + str(n2))
        lock.release()
        lock.release()

n1, n2 = 0, 0
lock = threading.RLock()

if __name__ == '__main__':
    thread_list = []
    for i in range(5):
        t = MyThread()
        t.start()
        thread_list.append(t)
    for t in thread_list:
        t.join()
    print('final num:%d ,%d' % (n1, n2))

输出:

Thread-1 set n1 to 1
Thread-1 set n2 to 1
Thread-2 set n1 to 2
Thread-2 set n2 to 3
Thread-3 set n1 to 3
Thread-3 set n2 to 6
Thread-4 set n1 to 4
Thread-4 set n2 to 10
Thread-5 set n1 to 5
Thread-5 set n2 to 15
final num:5 ,15

相关推荐