1. 程式人生 > >C# 多線程系列(五)

C# 多線程系列(五)

技術 多線程 post 生死 div 求和 設置 wid 按順序

死鎖

為了線程安全,我們在需要的是會使用”獨占鎖“,但過多的鎖定也會有麻煩。多個線程因為競爭資源相互等待而造成的僵局,我們稱為死鎖。若無外力作用,這些進程將都無法推進。在死鎖中,至少有兩個線程被掛起,並等待對方解除鎖定。

我們先看一個小例子:在一個平面上有很多方塊,有一些星星在這上面移動。基本規則就是:每個方塊只能有一個星星。

技術分享圖片(圖1)

移動過程如下圖,小黑想從a2方塊移動到a3方塊。這時小黑得占用a2位置,當a3位置為空時跳過去。

技術分享圖片(圖2)

當想移動的目標位置被其他星星占著的時候,就等其他星星移走後再移動過去。如下圖小綠,會等到小黑移走後再移動到a2。

技術分享圖片(圖3)

但當小黑想移動的目標位置是b2的時候就悲劇了,固執的星星小黑和小綠將相互等待對方移走,直到永遠。

技術分享圖片(圖4)

可能會出現更復雜的情況,小黑在等待小紅移走,小紅在等待小藍移走,小藍在等待小綠移走,小綠在等待小黑移走。這樣僵持著!

技術分享圖片(圖5)

或更更復雜的情況(腦補)。。。

產生死鎖的條件

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

  • 互斥條件:進程要求對所分配的資源進行排他性控制,即在一段時間內某資源僅為一個進程所占有。此時若有其他進程請求該資源,則請求進程只能等待。對應我們例子就是,每個方塊只能容的下一個星星。
  • 不剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能由獲得該資源的進程自己來釋放(只能是主動釋放)。對應我們的例子就是,星星只能等待目標位置星星自己走,而不能把它推下方塊。
  • 請求和保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他進程占有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。對應我們的例子就是,星星得占著一個方塊,然後找目標方塊。
  • 循環等待條件:存在一種進程資源的循環等待鏈,鏈中每一個進程已獲得的資源同時被鏈中下一個進程所請求。

預防死鎖

從我們的例子出發,我們能想到的方法有: 1. 讓星星只能按順序跳,例如規定的順序是a0...an...b1...bn...c1...cn。當然,有能力的星星可以跨越著跳,例如從a0到c1,但不能違背順序逆著來。當到想回頭的時候,就必須先下了方塊(釋放鎖)再從頭開始。 2. 當星星想移動的目標位置被其他星星占著的時候,就等其他星星移走後再移動過去。這是星星的移動規則,我們可以在這裏加個時限,當等待的時間超過時限,就運行星星“休息”,星星就先下方塊(釋放鎖),休息一段時間後再繼續。 3. 如果星星的移動線路是比較簡單的,例如每次啟動只移動5步,則可以采用預定的方法。星星開始移動的時候就鎖定所有將移動的方塊。 4. 改變星星的基本移動方法,例如從a0->a1,移動過程改成,先下方塊a0(釋放資源),然後再到a1上。 5. 在4的基礎上改進,每次都要下方塊比較不方便,所以我們改成,當需要等待移動目標方塊上星星移走的時候再先下方塊。 6. 找個管理者,當發現鎖死的時候,把這些固執的星星都叫下方塊。讓每個星星隨機等一段時間再繼續。
第1種方案:按照順序加鎖是一種有效的死鎖預防機制。但是,這種方式需要你事先知道所有可能會用到的鎖,但總有些時候是無法預知的。基本思想就是破壞產生死鎖的必要條件——循環等待條件。
第2種方案:是在嘗試獲取鎖的時候加一個超時時間,這也就意味著在嘗試獲取鎖的過程中若超過了這個時限該線程則放棄對該鎖請求。若一個線程沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退並釋放所有已經獲得的鎖,然後等待一段隨機的時間再重試。這段隨機的等待時間讓其它線程有機會嘗試獲取相同的這些鎖,並且讓該線程在沒有獲得鎖的時候可以繼續運行(加鎖超時後可以先繼續運行幹點其它事情,再回頭來重復之前加鎖的邏輯)。這算是一種預防,沒破壞死鎖的必要條件。且,如果多個線程同時請求幾個資源的時候,因為等待的時間有一樣,容易出現重復嘗試,始終得不到鎖。

第3種方案:這種方案比較暴力,杜絕了死鎖。但如果一個線程需要很多資源的時候,很浪費。且容易出現,因為所需的某一兩種資源不能滿足而不給分配資源。

第4、5種方案:這種方案破壞產生死鎖的必要條件——請求和保持條件。但並不是什麽情況下都能這麽用。

第6種方案:這個就是死鎖檢測,當發現死鎖的時候進行處理。

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

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

C# 多線程系列(五)