首頁 >常見問題 >如何避免死鎖?

如何避免死鎖?

coldplay.xixi
coldplay.xixi原創
2020-06-24 14:33:244535瀏覽

如何避免死鎖?

避免死鎖的方法:

當兩個執行緒互相等待對方釋放資源時,就會發生死鎖。 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 物件加鎖,兩個執行緒互相等待對方先釋放鎖,所以就出現了死鎖。

死鎖是不應該在程式中出現的,在編寫程式時應該盡量避免出現死鎖。 下面有幾種常見的方式用來解決死鎖問題:

  1. #避免多次鎖定。盡量避免同一個執行緒對多個 Lock 進行鎖定。例如上面的死鎖程序,主線程要對 A、B 兩個物件的 Lock 進行鎖定,副線程也要對 A、B 兩個物件的 Lock 進行鎖定,這就埋下了導致死鎖的隱患。

  2. 具有相同的加鎖順序。如果多個執行緒需要對多個 Lock 進行鎖定,則應該保證它們以相同的順序請求加鎖。例如上面的死鎖程序,主執行緒先對 A 物件的 Lock 加鎖,再對 B 物件的 Lock 加鎖;而副執行緒則先對 B 物件的 Lock 加鎖,再對 A 物件的 Lock 加鎖。這種加鎖順序很容易形成嵌套鎖定,進而導致死鎖。如果讓主執行緒、副執行緒依照相同的順序加鎖,就可以避免這個問題。

  3. 使用定時鎖定。程式在呼叫 acquire() 方法加鎖時可指定 timeout 參數,該參數指定超過 timeout 秒後會自動釋放對 Lock 的鎖定,這樣就可以解開死鎖了。

  4. 死鎖偵測。死鎖檢測是一種依靠演算法機制​​來實現的死鎖預防機制,它主要是針對那些不可能實現按序加鎖,也不能使用定時鎖的場景的。

#

以上是如何避免死鎖?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn