標籤:tor 計數器 for final _id 如何 避免 extman 方式
死結樣本
搞多線程的經常會遇到死結的問題,學習作業系統的時候會講到死結相關的東西,我們用Python直觀的示範一下。
死結的一個原因是互斥鎖。假設銀行系統中,使用者a試圖轉賬100塊給使用者b,與此同時使用者b試圖轉賬200塊給使用者a,則可能產生死結。
2個線程互相等待對方的鎖,互相佔用著資源不釋放。
#coding=utf-8 import time import threading class Account: def __init__(self, _id, balance, lock): self.id = _id self.balance = balance self.lock = lock def withdraw(self, amount): self.balance -= amount def deposit(self, amount): self.balance += amount def transfer(_from, to, amount): if _from.lock.acquire():#鎖住自己的賬戶 _from.withdraw(amount) time.sleep(1)#讓交易時間變長,2個交易線程時間上重疊,有足夠時間來產生死結 print ‘wait for lock...‘ if to.lock.acquire():#鎖住對方的賬戶 to.deposit(amount) to.lock.release() _from.lock.release() print ‘finish...‘ a = Account(‘a‘,1000, threading.Lock()) b = Account(‘b‘,1000, threading.Lock()) threading.Thread(target = transfer, args = (a, b, 100)).start() threading.Thread(target = transfer, args = (b, a, 200)).start()
防止死結的加鎖機制
問題:
你正在寫一個多線程程式,其中線程需要一次擷取多個鎖,此時如何避免死結問題。
解決方案:
在多線程程式中,死結問題很大一部分是由於線程同時擷取多個鎖造成的。舉個例子:一個線程擷取了第一個鎖,然後在擷取第二個鎖的 時候發生阻塞,那麼這個線程就可能阻塞其他線程的執行,從而導致整個程式假死。 解決死結問題的一種方案是為程式中的每一個鎖分配一個唯一的id,然後只允許按照升序規則來使用多個鎖,這個規則使用上下文管理器 是非常容易實現的,樣本如下:
import threadingfrom contextlib import contextmanager # Thread-local state to stored information on locks already acquired_local = threading.local() @contextmanagerdef acquire(*locks): # Sort locks by object identifier locks = sorted(locks, key=lambda x: id(x)) # Make sure lock order of previously acquired locks is not violated acquired = getattr(_local,‘acquired‘,[]) if acquired and max(id(lock) for lock in acquired) >= id(locks[0]): raise RuntimeError(‘Lock Order Violation‘) # Acquire all of the locks acquired.extend(locks) _local.acquired = acquired try: for lock in locks: lock.acquire() yield finally: # Release locks in reverse order of acquisition for lock in reversed(locks): lock.release() del acquired[-len(locks):]
如何使用這個上下文管理器呢?你可以按照正常途徑建立一個鎖對象,但不論是單個鎖還是多個鎖中都使用 acquire() 函數來申請鎖, 樣本如下:
import threadingx_lock = threading.Lock()y_lock = threading.Lock() def thread_1(): while True: with acquire(x_lock, y_lock): print(‘Thread-1‘) def thread_2(): while True: with acquire(y_lock, x_lock): print(‘Thread-2‘) t1 = threading.Thread(target=thread_1)t1.daemon = Truet1.start() t2 = threading.Thread(target=thread_2)t2.daemon = Truet2.start()
如果你執行這段代碼,你會發現它即使在不同的函數中以不同的順序擷取鎖也沒有發生死結。 其關鍵在於,在第一段代碼中,我們對這些鎖進行了排序。通過排序,使得不管使用者以什麼樣的順序來請求鎖,這些鎖都會按照固定的順序被擷取。 如果有多個 acquire() 操作被嵌套調用,可以通過執行緒區域儲存(TLS)來檢測潛在的死結問題。 假設你的代碼是這樣寫的:
import threadingx_lock = threading.Lock()y_lock = threading.Lock() def thread_1(): while True: with acquire(x_lock): with acquire(y_lock): print(‘Thread-1‘) def thread_2(): while True: with acquire(y_lock): with acquire(x_lock): print(‘Thread-2‘) t1 = threading.Thread(target=thread_1)t1.daemon = Truet1.start() t2 = threading.Thread(target=thread_2)t2.daemon = Truet2.start()
如果你運行這個版本的代碼,必定會有一個線程發生崩潰,異常資訊可能像這樣:
Exception in thread Thread-1:Traceback (most recent call last): File "/usr/local/lib/python3.3/threading.py", line 639, in _bootstrap_inner self.run() File "/usr/local/lib/python3.3/threading.py", line 596, in run self._target(*self._args, **self._kwargs) File "deadlock.py", line 49, in thread_1 with acquire(y_lock): File "/usr/local/lib/python3.3/contextlib.py", line 48, in __enter__ return next(self.gen) File "deadlock.py", line 15, in acquire raise RuntimeError("Lock Order Violation")RuntimeError: Lock Order Violation>>>
發生崩潰的原因在於,每個線程都記錄著自己已經擷取到的鎖。 acquire() 函數會檢查之前已經擷取的鎖列表, 由於鎖是按照升序排列擷取的,所以函數會認為之前已擷取的鎖的id必定小於新申請到的鎖,這時就會觸發異常。
討論
死結是每一個多線程程式都會面臨的一個問題(就像它是每一本作業系統課本的共同話題一樣)。根據經驗來講,儘可能保證每一個 線程只能同時保持一個鎖,這樣程式就不會被死結問題所困擾。一旦有線程同時申請多個鎖,一切就不可預料了。
死結的檢測與恢複是一個幾乎沒有優雅的解決方案的擴充話題。一個比較常用的死結檢測與恢複的方案是引入看門狗計數器。當線程正常 啟動並執行時候會每隔一段時間重設計數器,在沒有發生死結的情況下,一切都正常進行。一旦發生死結,由於無法重設計數器導致定時器 逾時,這時程式會通過重啟自身恢複到正常狀態。
避免死結是另外一種解決死結問題的方式,在進程擷取鎖的時候會嚴格按照對象id升序排列擷取,經過數學證明,這樣保證程式不會進入 死結狀態。證明就留給讀者作為練習了。避免死結的主要思想是,單純地按照對象id遞增的順序加鎖不會產生循環相依性,而循環相依性是 死結的一個必要條件,從而避免程式進入死結狀態。
下面以一個關於線程死結的經典問題:“哲學家就餐問題”,作為本節最後一個例子。題目是這樣的:五位哲學家圍坐在一張桌子前,每個人 面前有一碗飯和一隻筷子。在這裡每個哲學家可以看做是一個獨立的線程,而每隻筷子可以看做是一個鎖。每個哲學家可以處在靜坐、 思考、吃飯三種狀態中的一個。需要注意的是,每個哲學家吃飯是需要兩隻筷子的,這樣問題就來了:如果每個哲學家都拿起自己左邊的筷子, 那麼他們五個都只能拿著一隻筷子坐在那兒,直到餓死。此時他們就進入了死結狀態。 下面是一個簡單的使用死結避免機制解決“哲學家就餐問題”的實現:
import threading # The philosopher threaddef philosopher(left, right): while True: with acquire(left,right): print(threading.currentThread(), ‘eating‘) # The chopsticks (represented by locks)NSTICKS = 5chopsticks = [threading.Lock() for n in range(NSTICKS)] # Create all of the philosophersfor n in range(NSTICKS): t = threading.Thread(target=philosopher, args=(chopsticks[n],chopsticks[(n+1) % NSTICKS])) t.start()
Python中死結的形成樣本及死結情況的防止