文章目錄
1. 死鎖基本概念
多個併發進程中,如果每個進程持有某種資源而又等待其它進程釋放其他資源,在未改變這種狀態之前都不能向前推進,稱這一組進程產生了死鎖。通俗的講就是 多個進程無限期相互等待 的一種狀態。
2. 死鎖的四個必要條件
死鎖產生的 4 個條件(有一個條件不成立,則不會產生死鎖):
- 互斥:一個資源一次只能被一個進程使用
- 請求與保持:一個進程因請求資源而阻塞時,對已獲得資源保持不放
- 非搶佔:進程獲得的資源,在未完全使用完之前,不能強行搶佔
- 循環等待:若干進程之間形成一種頭尾相接的環形等待資源關係
搶佔資源和非搶佔資源:
- 可搶佔資源:可以從擁有它的進程中搶佔而不產生副作用。
- 不可搶佔資源:不引起相關的計算失敗的情況下,無法把它從佔有它的進程處搶佔過來。
一般來說,可搶佔資源不會引起死鎖,可以在進程間重新分配資源而得到解決。
資源分配圖(RAG):用有向圖描述系統資源和進程的狀態。
進程 P1 已經分得了兩個 R1 資源,並又請求一個 R2 資源;進程 P2 分得了一個 R1 和一個 R2 資源,並又請求一個 R1 資源。
死鎖發生時,系統中一定有由兩個或以上的進程組成的一條環路,該環路中的每個進程都在等待着下一個進程所佔有的資源。用一個有向圖來表示資源分配的情況。用圓形節點表示進程,方形表示資源。從 資源節點到進程節點的有向邊表示該資源被請求、並被進程佔用,從 進程到資源節點的有向邊表示進程正在請求該資源,並且因爲請求資源而導致進程被阻塞,處於等待該資源的狀態。一旦在某個時候有向圖中出現了 兩個或兩個以上進程組成的環路,就會導致死鎖的發生。
死鎖定理:如果資源分配圖中沒有環路,則系統中沒有死鎖,如果圖中存在環路則系統中可能存在死鎖;如果每個資源類中只包含一個資源實例,則環路是死鎖存在的充分必要條件。
3. 死鎖處理方法
死鎖的四個處理方法:
- 鴕鳥策略:忽略掉死鎖,視而不見
- 死鎖檢測與恢復:允許死鎖發生,檢測它們是否發生,一旦發生死鎖,就採取行動解決問題
- 死鎖避免:仔細對資源進行分配,動態地避免死鎖
- 死鎖預防:破壞引起死鎖的 4 個必要條件
3.1 鴕鳥策略
把頭埋在沙子裏,假裝根本沒發生問題。
因爲解決死鎖問題的代價很高,因此鴕鳥策略這種不採取任務措施的方案會獲得更高的性能。
當發生死鎖時不會對用戶造成多大影響,或發生死鎖的概率很低,就可以採用鴕鳥策略。
大多數操作系統,包括 Unix,Linux 和 Windows,處理死鎖問題的辦法僅僅是忽略它。
3.2 死鎖檢測與恢復
不試圖阻止死鎖,而是當檢測到死鎖發生時,採取措施進行恢復。
死鎖檢測:
- 每種類型一個資源的死鎖檢測:檢測有向圖中是否存在環。從一個節點出發進行深度優先搜索,對訪問過的節點進行標記,如果訪問了已經標記的節點,就表示有向圖存在環,也就是檢測到了死鎖。
- 每種類型多個資源的死鎖檢測:每個進程最開始時都不被標記(未執行狀態),執行過程有可能被標記。當算法結束時,任何沒有被標記的進程都是死鎖進程。
三個典型的檢測時機:
- 當進程由於資源請求不滿足而等待時檢測死鎖,缺點是系統開銷大
- 定時檢測
- 系統資源利用率下降時檢測死鎖
每種類型多個資源的死鎖檢測算法:
- 尋找一個沒有標記的進程 Pi,要求它所請求的資源小於等於剩餘資源量。
- 如果找到了這樣一個進程,就將其佔有的資源量加到剩餘資源量中,並標記該進程,轉回第 1 步。
- 如果沒有找到這樣一個進程,說明發生死鎖。
死鎖恢復:
- 搶佔恢復:死鎖發生的必要條件,其中一個就是不可搶佔。如果允許搶佔,那麼就可以破壞死鎖條件。
- 回滾恢復:週期性對進程進行檢查點檢查,一旦發現了死鎖,就回滾到一個較早的檢查點上。
- 殺死進程恢復:殺死一個進程可以釋放它佔有的資源,如果仍然不行那麼久繼續殺死其他進程直到打破死鎖。
3.3 死鎖避免
在程序運行時避免發生死鎖,包括兩種方法:1)資源軌跡圖,2)銀行家算法。
1. 資源軌跡圖
如果知道了進程在各個階段需要哪些資源,那麼可以在圖中進程標註。兩個進程的交疊區域就是一個會造成死鎖的區域。進程在圖中只能向右或者向上前進,一旦進入了危險區,那麼就可能發生死鎖。爲了避免死鎖,應當在合適的時間阻塞某個進程,使得運行避開這個區域。
2. 銀行家算法
銀行家算法的主要思想是避免系統進入不安全狀態。在每次進行資源分配時,它首先檢查系統是否有足夠的資源滿足要求,如果有,則先進行分配,並對分配後的新狀態進行安全性檢查。如果新狀態安全,則正式分配上述資源,否則就拒絕分配上述資源。這樣,它保證系統始終處於安全狀態,從而避免死鎖現象的發生。
安全狀態
如果沒有死鎖發生,並且即使所有進程突然請求對資源的最大需求,也仍然存在某種調度次序能夠使得每一個進程運行完畢,則稱該狀態是安全的(安全狀態一定沒有死鎖發生)。
安全狀態與不安全狀態的區別是,從安全狀態出發,系統能夠保證所有的進程都能完成;而從不安全狀態出發,沒有這樣的保證。
單個資源的銀行家算法
一個小城鎮的銀行家,他向一羣客戶分別承諾了一定的貸款額度,算法要做的是對每一個請求進行檢查,檢查如果滿足了這一需求是否會達到安全狀態。如果能,那麼滿足該需求;如果不能,就推遲對這一請求的滿足。
銀行家算法可以推廣到多個資源的情況,此時可以寫成矩陣的形式,每次判斷一行是否滿足,即一個進程的多個資源都進行檢查。
多個資源的銀行家算法
檢查一個狀態是否安全的算法:
- 查找右邊的矩陣是否存在一行小於等於向量 A。如果不存在這樣的行,那麼系統將會發生死鎖,狀態是不安全的。
- 假若找到這樣一行,將該進程標記爲終止,並將其已分配資源加到 A 中。
- 重複以上兩步,直到所有進程都標記爲終止,則狀態時安全的。
如果一個狀態不是安全的,需要拒絕進入這個狀態。
實際上,死鎖避免是非常困難的,無論是資源軌跡圖還是銀行家算法,都需要事先知道進程運行的過程中需要的最大資源數,這幾乎是不可能實現的。
3.4 死鎖預防
死鎖預防就是在程序運行之前預防發生死鎖,即破壞發生死鎖的 4 個必要條件。死鎖避免可以認爲是在程序執行中動態地避免死鎖發生,而死鎖預防可以說是靜態的方式,杜絕死鎖發生的可能性。
1. 破壞互斥條件:儘量使得資源不被某個進程獨佔。
但有些資源不能同時訪問,如打印機等臨界資源只能互斥使用。所以在某些場合應該保護這種互斥性。
2. 破壞佔有和等待條件:禁止已經持有資源的進程再等待其他資源。一種方式是,在進程開始執行前請求所需的全部資源(預先靜態分配的方法),如果不能滿足,那麼就不分配資源,進行等待。這種方式的問題在於,類似銀行家算法,事先不知道需要多少資源,而且資源利用率不高。另一種方式就是,當一個進程請求資源時,先暫時釋放其當前所佔用的所有資源,然後再嘗試一次獲取所需的全部資源。
這種方式實現簡單,但缺點也顯而易見,系統資源被嚴重浪費,其中有些資源可能僅在運行初期或運行快結束時才使用,甚至根本不使用。而且還會導致“飢餓”現象,當由於個別資源長期被其他進程佔用時,將致使等待該資源的進程遲遲不能開始運行。
3. 破壞不可搶佔條件:允許資源搶佔即可。當然,有的時候資源應當是不可搶佔的。
該策略釋放已獲得的資源可能造成前一階段工作的失效,反覆地申請和釋放資源會增加系統開銷,降低系統吞吐量。這種方法常用於狀態易於保存和恢復的資源,如 CPU 的寄存器及內存資源,一般不能用於打印機之類的資源。
4. 破壞環路等待:一種方法是對資源進行編號,進程在任何時候都可以請求資源,但是所有的請求必須按照資源編號的順序(升序)提出。
這種方法存在的問題是,編號必須相對穩定,這就限制了新類型設備的增加;儘管在爲資源編號時已考慮到大多數作業實際使用這些資源的順序,但也經常會發生作業使甩資源的順序與系統規定順序不同的情況,造成資源的浪費;此外,這種按規定次序申請資源的方法,也必然會給用戶的編程帶來麻煩。
3.5 死鎖處理策略比較
資源分配策略 | 模式 | 優點 | 缺點 |
---|---|---|---|
死鎖檢測 | 寬鬆,只要允許就分配資源 | 定期檢查死鎖是否已經發生 | 不延長進程初始化時間,允許對死鎖進行現場處理 |
死鎖避免 | 是”預防“和”檢測“ 的折中(在運行時判斷是否可能死鎖) | 尋找可能的安全允許順序 | 不必進行剝奪 |
死鎖預防 | 保守,寧可資源閒置 | 一次請求所有資源,資 源剝奪,資源按序分配 | 適用於做突發式處理的進程,不必進行剝奪 |
4. 其他相關
4.1 活鎖
活鎖指進程並沒有被阻塞,但由於某些條件沒有滿足,導致一直重複嘗試、失敗、嘗試、失敗。進程仍可以在 CPU 上活動,但 CPU 時間片執行完了之後又下了 CPU,進程沒有任何進展但也沒有阻塞。
活鎖和死鎖的區別:
- 死鎖有可能自行解開,而死鎖不能
- 活鎖有 CPU 執行權,而死鎖進程是無法獲得 CPU 執行權的
4.2 飢餓
飢餓:進程無限等待的情況。飢餓 ≠ 死鎖,但是飢餓至少有一個進程的執行被無限期推遲。
產生飢餓的原因:往往是由於資源分配策略的 “不公平性” 導致的,比如短作業優先。此時系統雖然沒有發生死鎖,某些進程也可能會一直得不到 CPU 的使用權而長時間等待。當 “飢餓” 到一定程度,進程任務即使完成也不再具有實際意義時稱該進程被 “餓死”。
飢餓與死鎖都是由於 資源競爭 而引起的。
飢餓與死鎖的差別:
- 進入“飢餓”狀態的進程可以只有一個,而由於循環等待條件而進入死鎖狀態的進程卻必須大於或等於兩個。
- 處於“飢餓”狀態的進程可以是一個就緒進程,如靜態優先權調度算法時的低優先權進程,而處於死鎖狀態的進程則必定是阻塞進程。
- 死鎖進程等待的是永遠不會被釋放的資源,而餓死進程等待的是會釋放但不會分配給自己的資源。
- 死鎖一定發生了循環等待,而餓死不一定。可以通過資源分配圖檢測是否死鎖,但不能檢測是否有進程餓死。
4.3 通信死鎖
當一個進程 A 向 B 發送信息後掛起,需要 B 進程的回覆喚醒時,如果請求信息丟失,A 就會被阻塞以等待回覆,B 會阻塞等待一個向其發送命令的請求,因而發生死鎖。
4.4 兩階段加鎖
這是針對數據庫的一種方法。第一階段中,進程試圖對 所有需要更新的記錄 進行加鎖,一旦某個記錄已經被加鎖,就釋放之前的鎖,從頭進行重試。只有當第一階段所有獲取鎖的行爲都成功,才進行第二階段的更新,否則放棄所有的鎖。