避免死鎖的方法:
當兩個執行緒互相等待對方釋放資源時,就會發生死鎖。 Python 解釋器沒有監測,也不會主動採取措施來處理死鎖情況,所以在進行多執行緒程式設計時應該採取措施避免死鎖。
一旦出現死鎖,整個程式既不會發生任何異常,也不會給予任何提示,只是所有執行緒都處於阻塞狀態,無法繼續。
死鎖是很容易發生的,尤其是在系統中出現多個同步監視器的情況下,如下程序將會出現死鎖:
import threading import time class A: def __init__(self): self.lock = threading.RLock() def foo(self, b): try: self.lock.acquire() print("当前线程名: " + threading.current_thread().name\ + " 进入了A实例的foo()方法" ) # ① time.sleep(0.2) print("当前线程名: " + threading.current_thread().name\ + " 企图调用B实例的last()方法") # ③ b.last() finally: self.lock.release() def last(self): try: self.lock.acquire() print("进入了A类的last()方法内部") finally: self.lock.release() class B: def __init__(self): self.lock = threading.RLock() def bar(self, a): try: self.lock.acquire() print("当前线程名: " + threading.current_thread().name\ + " 进入了B实例的bar()方法" ) # ② time.sleep(0.2) print("当前线程名: " + threading.current_thread().name\ + " 企图调用A实例的last()方法") # ④ a.last() finally: self.lock.release() def last(self): try: self.lock.acquire() print("进入了B类的last()方法内部") finally: self.lock.release() a = A() b = B() def init(): threading.current_thread().name = "主线程" # 调用a对象的foo()方法 a.foo(b) print("进入了主线程之后") def action(): threading.current_thread().name = "副线程" # 调用b对象的bar()方法 b.bar(a) print("进入了副线程之后") # 以action为target启动新线程 threading.Thread(target=action).start() # 调用init()函数 init()
執行上面程序,將會看到如圖1 所示的效果。
圖1 死鎖效果
從圖1 可以看出,程式既無法向下執行,也不會拋出任何異常,就一直「僵持」著。究其原因,是因為上面程式中 A 物件和 B 物件的方法都是執行緒安全的方法。
程式中有兩個執行緒執行,副執行緒的執行緒執行體是 action() 函數,主執行緒的執行緒執行體是 init() 函數(主程式呼叫了 init() 函數)。其中在 action() 函數中讓 B 物件呼叫 bar() 方法,而在 init() 函數中讓 A 物件呼叫 foo() 方法。
圖1 顯示action() 函數先執行,呼叫了B 物件的bar() 方法,在進入bar() 方法之前,該執行緒對B 物件的Lock 加鎖(當程式執行到② 號程式碼時,副線程暫停0.2s);CPU 切換到執行另一個線程,讓A 物件執行foo() 方法,所以看到主線程開始執行A 實例的foo() 方法,在進入foo() 方法之前,此執行緒對A 物件的Lock 加鎖(當程式執行到① 號程式碼時,主執行緒也暫停0.2s)。
接下來副線程會先醒過來,繼續向下執行,直到執行到④ 號程式碼處希望呼叫A 物件的last() 方法(在執行該方法之前,必須先對A 物件的Lock加鎖),但此時主執行緒正保持著A 物件的Lock 的鎖定,所以副執行緒被阻塞。
接下來主執行緒應該也醒過來了,繼續向下執行,直到執行到③ 號程式碼處希望呼叫B 物件的last() 方法(在執行該方法之前,必須先對B 物件的Lock 加鎖),但此時副執行緒並沒有釋放對B 物件的Lock 的鎖定。
至此,就出現了主執行緒保持著A 物件的鎖,等待對B 物件加鎖,而副執行緒保持著B物件的鎖,等待對A 物件加鎖,兩個執行緒互相等待對方先釋放鎖,所以就出現了死鎖。
死鎖是不應該在程式中出現的,在編寫程式時應該盡量避免出現死鎖。 下面有幾種常見的方式用來解決死鎖問題:
#避免多次鎖定。盡量避免同一個執行緒對多個 Lock 進行鎖定。例如上面的死鎖程序,主線程要對 A、B 兩個物件的 Lock 進行鎖定,副線程也要對 A、B 兩個物件的 Lock 進行鎖定,這就埋下了導致死鎖的隱患。
具有相同的加鎖順序。如果多個執行緒需要對多個 Lock 進行鎖定,則應該保證它們以相同的順序請求加鎖。例如上面的死鎖程序,主執行緒先對 A 物件的 Lock 加鎖,再對 B 物件的 Lock 加鎖;而副執行緒則先對 B 物件的 Lock 加鎖,再對 A 物件的 Lock 加鎖。這種加鎖順序很容易形成嵌套鎖定,進而導致死鎖。如果讓主執行緒、副執行緒依照相同的順序加鎖,就可以避免這個問題。
使用定時鎖定。程式在呼叫 acquire() 方法加鎖時可指定 timeout 參數,該參數指定超過 timeout 秒後會自動釋放對 Lock 的鎖定,這樣就可以解開死鎖了。
死鎖偵測。死鎖檢測是一種依靠演算法機制來實現的死鎖預防機制,它主要是針對那些不可能實現按序加鎖,也不能使用定時鎖的場景的。
以上是如何避免死鎖?的詳細內容。更多資訊請關注PHP中文網其他相關文章!