多線程中一個線程須要一次得到多個鎖,怎麼才能實現不會出現死鎖的狀況。 多線程
在多線程程序中,死鎖問題很大一部分是因爲線程同時獲取多個鎖形成的。舉個例子:一個線程獲取了第一個鎖,而後在獲取第二個鎖的 時候發生阻塞,那麼這個線程就可能阻塞其餘線程的執行,從而致使整個程序假死。 解決死鎖問題的一種方案是爲程序中的每個鎖分配一個惟一的id,而後只容許按照升序規則來使用多個鎖,這個規則使用上下文管理器 是很是容易實現的,示例以下:函數
1 import threading 2 from contextlib import contextmanager 3 4 _local = threading.local() # 獲取一個_local對象 5 6 @contextmanager 7 def acquire(*locks): 8 locks = sorted(locks, key=lambda x:id(x)) # 把傳入的鎖排個序 9 10 acquired = getattr(_local, "acquired", []) # 看一下當前線程有沒有acquired屬性,沒有就返回一個列表 11 if acquired and max(id(lock) for lock in acquired) >= id(locks[0]): # 若是不是空列表就有這個屬性, 12 # 若是當前線程的acquired列表中最大的鎖比傳入的最小的鎖要大的話,說明傳入的鎖中有已經被acquire的鎖了,不能再進行acquire 13 raise RuntimeError("Lock Order Violation") 14 15 acquired.extemd(locks) # 若是沒有出現已經被acquire的鎖,咱們就把這些鎖和當前線程已經有的鎖放在一塊兒 16 _local.acquired = acquired 17 18 try: 19 for lock in locks: # 而後按照順序依次地acquire鎖 20 lock.acquire() 21 yield 22 23 finally: 24 for lock in reversed(locks): # 上下文結束以後按照上鎖的順序的逆序進行解鎖 25 lock.release() 26 del acquired[-len(locks):] # 而後刪除掉這些lock
使用方法,能夠按照正常途徑建立一個鎖對象,但不管是單個鎖仍是多個鎖中都使用 acquire()
函數來申請鎖, 示例以下:ui
1 import threading 2 x_lock = threading.Lock() 3 y_lock = threading.Lock() 4 5 def thread_1(): 6 while True: 7 with acquire(x_lock, y_lock): 8 print('Thread-1') 9 10 def thread_2(): 11 while True: 12 with acquire(y_lock, x_lock): 13 print('Thread-2') 14 15 t1 = threading.Thread(target=thread_1) 16 t1.daemon = True 17 t1.start() 18 19 t2 = threading.Thread(target=thread_2) 20 t2.daemon = True 21 t2.start()
死鎖是每個多線程程序都會面臨的一個問題(就像它是每一本操做系統課本的共同話題同樣)。根據經驗來說,儘量保證每個 線程只能同時保持一個鎖,這樣程序就不會被死鎖問題所困擾。一旦有線程同時申請多個鎖,一切就不可預料了。spa
死鎖的檢測與恢復是一個幾乎沒有優雅的解決方案的擴展話題。一個比較經常使用的死鎖檢測與恢復的方案是引入看門狗計數器。當線程正常 運行的時候會每隔一段時間重置計數器,在沒有發生死鎖的狀況下,一切都正常進行。一旦發生死鎖,因爲沒法重置計數器致使定時器 超時,這時程序會經過重啓自身恢復到正常狀態。操作系統
避免死鎖是另一種解決死鎖問題的方式,在進程獲取鎖的時候會嚴格按照對象id升序排列獲取,通過數學證實,這樣保證程序不會進入 死鎖狀態。避免死鎖的主要思想是,單純地按照對象id遞增的順序加鎖不會產生循環依賴,而循環依賴是 死鎖的一個必要條件,從而避免程序進入死鎖狀態。線程