互斥锁、死锁和递归锁
一、互斥锁(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