互斥鎖、死結和遞迴鎖,互斥鎖遞迴
一、互斥鎖(Mutex)
在上節最後我們講到了安全執行緒,線程同步能夠保證多個安全執行緒訪問競爭資源,最簡單的同步機制是引入互斥鎖。互斥鎖為資源引入一個狀態:鎖定/非鎖定。某個線程要更改共用資料時,先將其鎖定,此時資源的狀態為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下資料的正確性。
threading模組中定義了Lock類,可以方便的處理鎖定:
#建立鎖lock = threading.Lock()#鎖定lock.acquire(blocking=True, timeout=-1) #釋放 lock.release()
還是以上節執行個體為例:
# -*- coding: UTF-8 -*-import threadingclass MyThread(threading.Thread): def run(self): global n lock.acquire() n += 1 lock.release() print(self.name + ' set n to ' + str(n))n = 0lock = threading.Lock()if __name__ == '__main__': for i in range(5): t = MyThread() t.start() print('final num: %d' % n)
輸出:
Thread-1 set n to 1Thread-2 set n to 2Thread-3 set n to 3Thread-4 set n to 4Thread-5 set n to 5final num: 5
加鎖之後,我們可以確保資料的正確性
二、死結
死結是指一個資源被多次調用,而多次調用方都未能釋放該資源就會造成一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死結狀態或系統產生了死結。
2.1 一個線程內部多次加鎖卻沒有釋放
import threadingclass 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, 0lock = 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 numdef run2(): print("grab the second part data") lock.acquire() global num2 num2+=1 lock.release() return num2def 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 threadingclass 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, 0lock = 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 1Thread-1 set n2 to 1Thread-2 set n1 to 2Thread-2 set n2 to 3Thread-3 set n1 to 3Thread-3 set n2 to 6Thread-4 set n1 to 4Thread-4 set n2 to 10Thread-5 set n1 to 5Thread-5 set n2 to 15final num:5 ,15