1. 程式人生 > >多線程死鎖的產生原因以及如何避免

多線程死鎖的產生原因以及如何避免

多線程 相同 兩個 原因 如何 競爭 問題 註意 必須

多線程以改善了系統資源的利用率並且提高了系統的處理能力。但是,並發執行同時也帶來了新的問題——死鎖。所謂的死鎖就是多個線程因競爭資源而造成的一種互相等待,如果沒有外力作用,這些線程都將無法繼續執行

死鎖產生的原因

系統資源的競爭

通常系統中擁有的不可剝奪資源,其數量不足以滿足多個線程運行的需要,使得線程在 運行過程中,會因爭奪資源而陷入僵局,如磁帶機、打印機等。只有對不可剝奪資源的競爭 才可能產生死鎖,對可剝奪資源的競爭是不會引起死鎖的。

線程推進順序非法

線程在運行過程中,請求和釋放資源的順序不當,也同樣會導致死鎖。例如,並發線程 P1、P2分別保持了資源R1、R2,而線程P1申請資源R2,線程P2申請資源R1時,兩者都 會因為所需資源被占用而阻塞。

信號量使用不當也會造成死鎖。線程間彼此相互等待對方發來的消息,結果也會使得這 些線程間無法繼續向前推進。例如,線程A等待線程B發的消息,線程B又在等待線程A 發的消息,可以看出線程A和B不是因為競爭同一資源,而是在等待對方的資源導致死鎖。

死鎖產生的必要條件

產生死鎖必須同時滿足以下四個條件,只要其中任一條件不成立,死鎖就不會發生。

  1. 互斥條件:線程要求對所分配的資源(如打印機)進行排他性控制,即在一段時間內某資源僅為一個線程所占有。此時若有其他線程請求該資源,則請求線程只能等待
  2. 不剝奪條件:線程所獲得的資源在未使用完畢之前,不能被其他線程強行奪走,即只能 由獲得該資源的線程自己來釋放(只能是主動釋放)
  3. 請求和保持條件:線程已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他線程占有,此時請求線程被阻塞,但對自己已獲得的資源保持不放
  4. 循環等待條件:存在一種線程資源的循環等待鏈,鏈中每一個線程已獲得的資源同時被 鏈中下一個線程所請求。即存在一個處於等待狀態的線程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的資源被P0占有

如何避免

在有些情況下死鎖是可以避免的。三種用於避免死鎖的技術:

  1. 加鎖順序
  2. 加鎖時限
  3. 死鎖檢測

加鎖順序

當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就容易發生。

按照順序加鎖是一種有效的死鎖預防機制。但是,這種方式需要事先知道所有可能會用到的鎖,但總有些時候是無法預知的。

加鎖時限

當一個線程在嘗試獲取鎖的過程中超過了這個時限則該線程應該放棄對該鎖進行請求。

若一個線程沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退並釋放所有已經獲得的鎖,然後等待一段隨機的時間再重試。這段隨機的等待時間讓其它線程有機會嘗試獲取相同的這些鎖,並且讓該應用在沒有獲得鎖的時候可以繼續運行。

需要註意的是,由於存在鎖的超時,所以我們不能認為這種場景就一定是出現了死鎖。也可能是因為獲得了鎖的線程(導致其它線程超時)需要很長的時間去完成它的任務。

此外,如果有非常多的線程同一時間去競爭同一批資源,就算有超時和回退機制,還是可能會導致這些線程重復地嘗試但卻始終得不到鎖。如果只有兩個線程,並且重試的超時時間設定為0到500毫秒之間,這種現象可能不會發生,但是如果是10個或20個線程情況就不同了。因為這些線程等待相等的重試時間的概率就高的多(或者非常接近以至於會出現問題)。

死鎖檢測

死鎖檢測是一個更好的死鎖預防機制,它主要是針對那些不可能實現按序加鎖並且鎖超時也不可行的場景。

每當一個線程獲得了鎖,會在線程和鎖相關的數據結構中(map、graph等等)將其記下。除此之外,每當有線程請求鎖,也需要記錄在這個數據結構中。

當一個線程請求鎖失敗時,這個線程可以遍歷鎖的關系圖看看是否有死鎖發生。

那麽當檢測出死鎖時,這些線程該做些什麽呢?

一個可行的做法是釋放所有鎖,回退,並且等待一段隨機的時間後重試。這個和簡單的加鎖超時類似,不一樣的是只有死鎖已經發生了才回退,而不會是因為加鎖的請求超時了。雖然有回退和等待,但是如果有大量的線程競爭同一批鎖,它們還是會重復地死鎖(編者註:原因同超時類似,不能從根本上減輕競爭)。

一個更好的方案是給這些線程設置優先級,讓一個(或幾個)線程回退,剩下的線程就像沒發生死鎖一樣繼續保持著它們需要的鎖。如果賦予這些線程的優先級是固定不變的,同一批線程總是會擁有更高的優先級。為避免這個問題,可以在死鎖發生的時候設置隨機的優先級。

多線程死鎖的產生原因以及如何避免