一、互斥鎖(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