1. 程式人生 > >什麼是死鎖,發生原因是什麼,如何解決和避免產生死鎖?

什麼是死鎖,發生原因是什麼,如何解決和避免產生死鎖?

一、什麼是死鎖?

死鎖是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。是作業系統層面的一個錯誤,是程序死鎖的簡稱,最早在 1965 年由 Dijkstra 在研究銀行家演算法時提出的,它是計算機作業系統乃至整個併發程式設計領域最難處理的問題之一。

事實上,計算機世界有很多事情需要多執行緒方式去解決,因為這樣才能最大程度上利用資源,才能體現出計算的高效。但是,實際上來說,計算機系統中有很多一次只能由一個程序使用的資源的情況,例如印表機,同時只能有一個程序控制它。在多通道程式設計環境中,若干程序往往要共享這類資源,而且一個程序所需要的資源還很有可能不止一個。因此,就會出現若干程序競爭有限資源,又推進順序不當,從而構成無限期迴圈等待的局面。我們稱這種狀態為死鎖。簡單一點描述,死鎖是指多個程序迴圈等待它方佔有的資源而無限期地僵持下去的局面。很顯然,如果沒有外力的作用,那麼死鎖涉及到的各個程序都將永遠處於封鎖狀態。

系統發生死鎖現象不僅浪費大量的系統資源,甚至導致整個系統崩潰,帶來災難性後果。所以,對於死鎖問題在理論上和技術上都必須予以高度重視。

二、死鎖發生的原因?

A:java 死鎖產生的四個必要條件
1、互斥使用,即當資源被一個執行緒使用(佔有)時,別的執行緒不能使用
2、不可搶佔,資源請求者不能強制從資源佔有者手中奪取資源,資源只能由資源佔有者主動釋放。
3、請求和保持,即當資源請求者在請求其他的資源的同時保持對原有資源的佔有。
4、迴圈等待,即存在一個等待佇列:P1佔有P2的資源,P2佔有P3的資源,P3佔有P1的資源。這樣就形成了一個等待環路。
B:產生原因:


競爭資源引起程序死鎖
當系統中供多個程序共享的資源如印表機、公用佇列的等,其數目不足以滿足諸程序的需要時,會引起諸程序對資源的競爭而產生死鎖。
可剝奪資源和不可剝奪資源
系統中的資源可以分為兩類,一類是可剝奪資源,是指某程序在獲得這類資源後,該資源可以再被其他程序或系統剝奪。例如,優先權高的程序可以剝奪優先權低的程序的處理機。又如,記憶體區可由儲存器管理程式,把一個程序從一個儲存區移到另一個儲存區,此即剝奪了該程序原來佔有的儲存區,甚至可將一程序從記憶體調到外存上,可見,CPU和主存均屬於可剝奪性資源。另一類資源是不可剝奪資源,當系統把這類資源分配給某程序後,再不能強行收回,只能在程序用完後自行釋放,如磁帶機、印表機等。
競爭不可剝奪資源

在系統中所配置的不可剝奪資源,由於它們的數量不能滿足諸程序執行的需要,會使程序在執行過程中,因爭奪這些資源而陷於僵局。例如,系統中只有一臺印表機R1和一臺磁帶機R2,可供程序P1和P2共享。假定PI已佔用了印表機R1,P2已佔用了磁帶機R2,若P2繼續要求印表機R1,P2將阻塞;P1若又要求磁帶機,P1也將阻塞。於是,在P1和P2之間就形成了僵局,兩個程序都在等待對方釋放自己所需要的資源,但是它們又都因不能繼續獲得自己所需要的資源而不能繼續推進,從而也不能釋放自己所佔有的資源,以致進入死鎖狀態。
競爭臨時資源
上面所說的印表機資源屬於可順序重複使用型資源,稱為永久資源。還有一種所謂的臨時資源,這是指由一個程序產生,被另一個程序使用,短時間後便無用的資源,故也稱為消耗性資源,如硬體中斷、訊號、訊息、緩衝區內的訊息等,它也可能引起死鎖。例如,SI,S2,S3是臨時性資源,程序P1產生訊息S1,又要求從P3接收訊息S3;程序P3產生訊息S3,又要求從程序P2處接收訊息S2;程序P2產生訊息S2,又要求從P1處接收產生的訊息S1。如果訊息通訊按如下順序進行:
P1: ···Relese(S1);Request(S3); ···
P2: ···Relese(S2);Request(S1); ···
P3: ···Relese(S3);Request(S2); ···
並不可能發生死鎖。但若改成下述的執行順序:
P1: ···Request(S3);Relese(S1);···
P2: ···Request(S1);Relese(S2); ···
P3: ···Request(S2);Relese(S3); ···
則可能發生死鎖。
2.程序推進順序不當引起死鎖
由於程序在執行中具有非同步性特徵,這可能使P1和P2兩個程序按下述兩種順序向前推進。
1) 程序推進順序合法
當程序P1和P2併發執行時,如果按照下述順序推進:P1:Request(R1); P1:Request(R2); P1: Relese(R1);P1: Relese(R2); P2:Request(R2); P2:Request(R1); P2: Relese(R2);P2: Relese(R1);這兩個程序便可順利完成,這種不會引起程序死鎖的推進順序是合法的。
2) 程序推進順序非法
若P1保持了資源R1,P2保持了資源R2,系統處於不安全狀態,因為這兩個程序再向前推進,便可能發生死鎖。例如,當P1執行到P1:Request(R2)時,將因R2已被P2佔用而阻塞;當P2執行到P2:Request(R1)時,也將因R1已被P1佔用而阻塞,於是發生程序死鎖。

三、如何避免(預防)和解決死鎖?

A:死鎖預防
理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和解除死鎖。只要打破四個必要條件之一就能有效預防死鎖的發生:打破互斥條件:改造獨佔性資源為虛擬資源,大部分資源已無法改造。打破不可搶佔條件:當一程序佔有一獨佔性資源後又申請一獨佔性資源而無法滿足,則退出原佔有的資源。打破佔有且申請條件:採用資源預先分配策略,即程序執行前申請全部資源,滿足則執行,不然就等待,這樣就不會佔有且申請。打破迴圈等待條件:實現資源有序分配策略,對所有裝置實現分類編號,所有程序只能採用按序號遞增的形式申請資源。 [3]
所以,在系統設計、程序排程等方面注意如何不讓這四個必要條件成立,如何確定資源的合理分配演算法,避免程序永久佔據系統資源。此外,也要防止程序在處於等待狀態的情況下佔用資源,在系統執行過程中,對程序發出的每一個系統能夠滿足的資源申請進行動態檢查,並根據檢查結果決定是否分配資源,若分配後系統可能發生死鎖,則不予分配,否則予以分配。因此,對資源的分配要給予合理的規劃。
下面幾種方法可用以避免重灌死鎖的發生:
①允許目的節點將不完整的報文遞交給目的端系統;
②一個不能完整重灌的報文能被檢測出來,並要求傳送該報文的源端系統重新傳送;
③為每個節點配備一個後備緩衝空間,用以暫存不完整的報文。
①、②兩種方法不能很滿意地解決重灌死鎖,因為它們使端系統中的協議複雜化了。一般的設計中,網路層應該對端系統透明,也即端系統不該考慮諸如報文拆、裝之類的事。③方法雖然不涉及端系統,但使每個節點增加了開銷。
有序資源分配法
這種演算法資源按某種規則系統中的所有資源統一編號(例如印表機為1、磁帶機為2、磁碟為3、等等),申請時必須以上升的次序。系統要求申請程序:
1、對它所必須使用的而且屬於同一類的所有資源,必須一次申請完;
2、在申請不同類資源時,必須按各類裝置的編號依次申請。例如:程序PA,使用資源的順序是R1,R2; 程序PB,使用資源的順序是R2,R1;若採用動態分配有可能形成環路條件,造成死鎖。
採用有序資源分配法:R1的編號為1,R2的編號為2;
PA:申請次序應是:R1,R2
PB:申請次序應是:R1,R2
這樣就破壞了環路條件,避免了死鎖的發生
銀行家演算法
避免死鎖演算法中最有代表性的演算法是Dijkstra E.W 於1968年提出的銀行家演算法:
銀行家演算法是避免死鎖的一種重要方法,防止死鎖的機構只能確保上述四個條件之一不出現,則系統就不會發生死鎖。通過這個演算法可以用來解決生活中的實際問題,如銀行貸款等。
程式實現思路銀行家演算法顧名思義是來源於銀行的借貸業務,一定數量的本金要應多個客戶的借貸週轉,為了防止銀行家資金無法週轉而倒閉,對每一筆貸款,必須考察其是否能限期歸還。在作業系統中研究資源分配策略時也有類似問題,系統中有限的資源要供多個程序使用,必須保證得到的資源的程序能在有限的時間內歸還資源,以供其他程序使用資源。如果資源分配不得到就會發生程序迴圈等待資源,則程序都無法繼續執行下去的死鎖現象。
把一個程序需要和已佔有資源的情況記錄在程序控制中,假定程序控制塊PCB其中“狀態”有就緒態、等待態和完成態。當程序在處於等待態時,表示系統不能滿足該程序當前的資源申請。“資源需求總量”表示程序在整個執行過程中總共要申請的資源量。顯然,每個程序的資源需求總量不能超過系統擁有的資源總數, 銀行演算法進行資源分配可以避免死鎖。

B:解決方法
在系統中已經出現死鎖後,應該及時檢測到死鎖的發生,並採取適當的措施來解除死鎖。
死鎖預防
這是一種較簡單和直觀的事先預防的方法。方法是通過設定某些限制條件,去破壞產生死鎖的四個必要條件中的一個或者幾個,來預防發生死鎖。預防死鎖是一種較易實現的方法,已被廣泛使用。但是由於所施加的限制條件往往太嚴格,可能會導致系統資源利用率和系統吞吐量降低。
死鎖避免
系統對程序發出的每一個系統能夠滿足的資源申請進行動態檢查,並根據檢查結果決定是否分配資源;如果分配後系統可能發生死鎖,則不予分配,否則予以分配。這是一種保證系統不進入死鎖狀態的動態策略。
死鎖檢測和解除
先檢測:這種方法並不須事先採取任何限制性措施,也不必檢查系統是否已經進入不安全區,此方法允許系統在執行過程中發生死鎖。但可通過系統所設定的檢測機構,及時地檢測出死鎖的發生,並精確地確定與死鎖有關的程序和資源。檢測方法包括定時檢測、效率低時檢測、程序等待時檢測等。
然後解除死鎖:採取適當措施,從系統中將已發生的死鎖清除掉。
這是與檢測死鎖相配套的一種措施。當檢測到系統中已發生死鎖時,須將程序從死鎖狀態中解脫出來。常用的實施方法是撤銷或掛起一些程序,以便回收一些資源,再將這些資源分配給已處於阻塞狀態的程序,使之轉為就緒狀態,以繼續執行。死鎖的檢測和解除措施,有可能使系統獲得較好的資源利用率和吞吐量,但在實現上難度也最大。

如果我們在死鎖檢查時發現了死鎖情況,那麼就要努力消除死鎖,使系統從死鎖狀態中恢復過來。消除死鎖的幾種方式:

  1. 最簡單、最常用的方法就是進行系統的重新啟動,不過這種方法代價很大,它意味著在這之前所有的程序已經完成的計算工作都將付之東流,包括參與死鎖的那些程序,以及未參與死鎖的程序;

  2. 撤消程序,剝奪資源。終止參與死鎖的程序,收回它們佔有的資源,從而解除死鎖。這時又分兩種情況:一次性撤消參與死鎖的全部程序,剝奪全部資源;或者逐步撤消參與死鎖的程序,逐步收回死鎖程序佔有的資源。一般來說,選擇逐步撤消的程序時要按照一定的原則進行,目的是撤消那些代價最小的程序,比如按程序的優先順序確定程序的代價;考慮程序執行時的代價和與此程序相關的外部作業的代價等因素;

  3. 程序回退策略,即讓參與死鎖的程序回退到沒有發生死鎖前某一點處,並由此點處繼續執行,以求再次執行時不再發生死鎖。雖然這是個較理想的辦法,但是操作起來系統開銷極大,要有堆疊這樣的機構記錄程序的每一步變化,以便今後的回退,有時這是無法做到的。

其實即便是商業產品,依然會有很多死鎖情況的發生,例如 MySQL 資料庫,它也經常容易出現死鎖案例。