死鎖應該有3個原因:
1.獨占資源
2.循環等待
3.不可剝奪
網上看到的4個條件,實際上還是3個,因為請求和等待導致的死鎖畢竟有點讓人難堪,實際上的死鎖多是因為請求等待導致的循環等待。
在用戶態,由於Linux提倡進程之間的公平,線程之間甚至沒有優先級之分,每個進程/線程都會有機會執行,所以不會出現那種高優先級進程/線程搶占低優先級之后,由於低優先級進程/線程的等待導致的死鎖,所以用戶態還不需要關心這樣的問題。
用戶態遇到的死鎖主要是循環等待,退幾步來說,可能就是在臨界區sleep的死鎖了,查這種死鎖很簡單,為鎖添加計數值就可以。
此外,之前提到pthread_mutex_t是具有優先級的,一個優先級比鎖的優先級高的線程無法對其加鎖,這防止了高優先級線程搶占低優先級線程的資源而導致飢餓,優先級導致的飢餓問題到內核態就變得非常麻煩。
還有一個就是自死鎖,重復對互斥量加鎖兩次,線程自己等待自己,還是屬於循環等待類型的。
在內核態,死鎖問題除了在用戶態的那些問題,最麻煩的就是存在不同的優先級與並發。
首先要明確,內核態的並發有以下幾個因素:
1.中斷並發,任何時候都可能發生,也是內核搶占的基礎之一,通過關中斷來禁止並發
2.多處理器並發,具備多個處理器核心時會發生,是真正的並發,通過加鎖來禁止並發
3.內核搶占並發,在支持內核搶占時會發生,屬於輪換並發,通過設置標識來禁止並發
4.調度並發,在進程上下文的切換時發生,內核共享處理器時間的基礎,不能禁止,但內核保證所有進程/線程都將得到執行,這跟用戶態的問題一致
中斷在系統中優先級最高,任何一個時間點上都可能發生,由於時序需要,它所執行的代碼都必須極度簡單,通過用戶態那樣的優先級策略來使加鎖失敗顯然不好,中斷上下文所面臨的主要問題有3個,第一是SMP系統的處理器並發,此時必須對共享數據加鎖;第二是它所打斷的代碼的並發,如果打斷的代碼已經加鎖,此時必是死鎖無疑,故而所有內核代碼,只要跟中斷處理程序共享鎖,必須先關中斷,防止死鎖;第三是中斷處理程序之間的並發,中斷可以打斷另外一個中斷,如果中斷處理程序之間共享鎖,這如果發生在同一個處理器上是會死鎖的,發生在不同處理器上則不會。總而言之,只要跟中斷處理程序共享的任何鎖都要關中斷,中斷的死鎖主要就是搶占引起的。
處理器並發的主要問題是為了優化效率,采用了一些處理器私有的數據,不必要加鎖,對這些數據的訪問必須禁止內核搶占,但不必要禁止中斷。禁止搶占的問題在於搶占的代碼會重新調度處理器,會訪問到錯誤的數據,這當然不是本文重點,重點是死鎖,多枚處理器如果出現互相搶占資源導致的循環等待簡直就是奇觀,CPU的核心之間相互循環等待,這有點太坑了。CPU並發使用多個資源應該按照一致的順序持有,出於優雅美觀的需求,按順序釋放也是非常必要的。處理器的死鎖主要是循環等待引起的。
從內核搶占的名字可以看出來,它的死鎖問題也主要是搶占引起的,但它不像中斷那樣優越,它必須滿足一堆的條件才能搶占,所以規避了很多的問題,實際需要注意的主要就剩下調度所產生的輪換並發了,這里有一篇不錯的博文可以看看,論述了內核搶占的機制。
調度並發實際上就是輪換使用處理器的結果,這導致任務的碎片化,它是進程抽象的基礎,它的代碼按邏輯執行,但任何一點都可能被搶占,所以它需要注意的就是,注意不要去惹中斷和處理器的私有數據,不得已要去弄一下了,記得關中斷,加鎖,禁止搶占啥的就OK了。