1. 程式人生 > >寫給大忙人看的死鎖全詳解

寫給大忙人看的死鎖全詳解

![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150443651-1541515149.png) ## 前言 計算機系統中有很多`獨佔性`的資源,在同一時刻只能每個資源只能由一個程序使用,我們之前經常提到過印表機,這就是一個獨佔性的資源,**同一時刻不能有兩個印表機同時輸出結果,否則會引起檔案系統的癱瘓**。所以,作業系統具有授權一個程序單獨訪問資源的能力。 兩個程序獨佔性的訪問某個資源,從而等待另外一個資源的執行結果,會導致兩個程序都被阻塞,並且兩個程序都不會釋放各自的資源,這種情況就是 `死鎖(deadlock)`。 死鎖可以發生在任何層面,在不同的機器之間可能會發生死鎖,在資料庫系統中也會導致死鎖,比如程序 A 對記錄 R1 加鎖,程序 B 對記錄 R2 加鎖,然後程序 A 和 B 都試圖把物件的記錄加鎖,這種情況下就會產生死鎖。 下面我們就來討論一下什麼是死鎖、死鎖的條件是什麼、死鎖如何預防、活鎖是什麼等。 首先你需要先了解一個概念,那就是資源是什麼 ## 資源 大部分的死鎖都和資源有關,在程序對裝置、檔案具有獨佔性(排他性)時會產生死鎖。我們把這類需要排他性使用的物件稱為`資源(resource)`。資源主要分為 **可搶佔資源和不可搶佔資源** ###可搶佔資源和不可搶佔資源 資源主要有可搶佔資源和不可搶佔資源。`可搶佔資源(preemptable resource)` 可以從擁有它的程序中搶佔而不會造成其他影響,記憶體就是一種可搶佔性資源,任何程序都能夠搶先獲得記憶體的使用權。 `不可搶佔資源(nonpreemtable resource)` 指的是除非引起錯誤或者異常,否則程序無法搶佔指定資源,這種不可搶佔的資源比如有光碟,在程序執行排程的過程中,其他程序是不能得到該資源的。 死鎖與不可搶佔資源有關,雖然搶佔式資源也會造成死鎖,不過這種情況的解決辦法通常是在程序之間重新分配資源來化解。所以,我們的重點自然就會放在了不可搶佔資源上。 下面給出了使用資源所需事件的抽象順序 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150451581-247119679.png) 如果在請求時資源不存在,請求程序就會強制等待。在某些作業系統中,當請求資源失敗時程序會自動阻塞,當自資源可以獲取時程序會自動喚醒。在另外一些作業系統中,請求資源失敗並顯示錯誤程式碼,然後等待程序等待一會兒再繼續重試。 請求資源失敗的程序會陷入一種**請求資源、休眠、再請求資源**的迴圈中。此類程序雖然沒有阻塞,但是處於從目的和結果考慮,這類程序和阻塞差不多,因為這類程序並沒有做任何有用的工作。 請求資源的這個過程是很依賴作業系統的。在一些系統中,一個 `request` 系統呼叫用來允許程序訪問資源。在一些系統中,作業系統對資源的認知是它是一種特殊檔案,在任何同一時刻只能被一個程序開啟和佔用。資源通過 `open` 命令進行開啟。如果檔案已經正在使用,那麼這個呼叫者會阻塞直到當前的佔用檔案的程序關閉檔案為止。 ### 資源獲取 對於一些資料庫系統中的記錄這類資源來說,應該由使用者程序來對其進行管理。有一種管理方式是使用`訊號量(semaphore)` 。這些訊號量會初始化為 1 。互斥鎖也能夠起到相同的作用。 > 這裡說一下什麼是`互斥鎖(Mutexes)`: > > 在計算機程式中,`互斥物件(mutex)` 是一個程式物件,它允許多個程式共享同一資源,例如檔案訪問許可權,但並不是同時訪問。需要鎖定資源的執行緒都必須在使用資源時將互斥鎖與其他執行緒繫結(進行加鎖)。當不再需要資料或執行緒結束時,互斥鎖設定為解鎖。 下面是一個虛擬碼,這部分程式碼說明了訊號量的資源獲取、資源釋放等操作,如下所示 ```c typedef int semaphore; semaphore aResource; void processA(void){ down(&aResource); useResource(); up(&aResource); } ``` 上面顯示了一個程序資源獲取和釋放的過程,但是一般情況下會存在多個資源同時獲取鎖的情景,這樣該如何處理?如下所示 ```c typedef int semaphore; semaphore aResource; semaphore bResource; void processA(void){ down(&aResource); down(&bResource); useAResource(); useBResource(); up(&aResource); up(&bResource); } ``` 對於單個程序來說,並不需要加鎖,因為不存在和這個程序的競爭條件。所以單進條件下程式能夠完好執行。 現在讓我們考慮兩個程序的情況,A 和 B ,還存在兩個資源。如下所示 ```c typedef int semaphore; semaphore aResource; semaphore bResource; void processA(void){ down(&aResource); down(&bResource); useBothResource(); up(&bResource); up(&aResource); } void processB(void){ down(&aResource); down(&bResource); useBothResource(); up(&bResource); up(&aResource); } ``` 在上述程式碼中,兩個程序以相同的順序訪問資源。在這段程式碼中,一個程序在另一個程序之前獲取資源,如果另外一個程序想在第一個程序釋放之前獲取資源,那麼它會由於資源的加鎖而阻塞,直到該資源可用為止。 在下面這段程式碼中,有一些變化 ```c typedef int semaphore; semaphore aResource; semaphore bResource; void processA(void){ down(&aResource); down(&bResource); useBothResource(); up(&bResource); up(&aResource); } void processB(void){ down(&bResource); // 變化的程式碼 down(&aResource); // 變化的程式碼 useBothResource(); up(&aResource); // 變化的程式碼 up(&bResource); // 變化的程式碼 } ``` 這種情況就不同了,可能會發生同時獲取兩個資源並有效地阻塞另一個過程,直到完成為止。也就是說,可能會發生程序 A 獲取資源 A 的同時程序 B 獲取資源 B 的情況。然後每個程序在嘗試獲取另一個資源時被阻塞。 在這裡我們會發現一個簡單的獲取資源順序的問題就會造成`死鎖`,所以死鎖是很容易發生的,所以下面我們就對死鎖做一個詳細的認識和介紹。 ## 死鎖 如果要對死鎖進行一個定義的話,下面的定義比較貼切 **如果一組程序中的每個程序都在等待一個事件,而這個事件只能由該組中的另一個程序觸發,這種情況會導致死鎖**。 簡單一點來表述一下,就是每個程序都在等待其他程序釋放資源,而其他資源也在等待每個程序釋放資源,這樣沒有程序搶先釋放自己的資源,這種情況會產生死鎖,所有程序都會無限的等待下去。 換句話說,死鎖程序結合中的每個程序都在等待另一個死鎖程序已經佔有的資源。但是由於所有程序都不能執行,它們之中任何一個資源都無法釋放資源,所以沒有一個程序可以被喚醒。這種死鎖也被稱為`資源死鎖(resource deadlock)`。資源死鎖是最常見的型別,但不是所有的型別,我們後面會介紹其他型別,我們先來介紹資源死鎖 ### 資源死鎖的條件 針對我們上面的描述,資源死鎖可能出現的情況主要有 * 互斥條件:每個資源都被分配給了一個程序或者資源是可用的 * 保持和等待條件:已經獲取資源的程序被認為能夠獲取新的資源 * 不可搶佔條件:分配給一個程序的資源不能強制的從其他程序搶佔資源,它只能由佔有它的程序顯示釋放 * 迴圈等待:死鎖發生時,系統中一定有兩個或者兩個以上的程序組成一個迴圈,迴圈中的每個程序都在等待下一個程序釋放的資源。 發生死鎖時,上面的情況必須同時會發生。如果其中任意一個條件不會成立,死鎖就不會發生。可以通過破壞其中任意一個條件來破壞死鎖,下面這些破壞條件就是我們探討的重點 ### 死鎖模型 Holt 在 1972 年提出對死鎖進行建模,建模的標準如下: * 圓形表示程序 * 方形表示資源 從資源節點到程序節點表示資源已經被程序佔用,如下圖所示 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150505619-1992784794.png) 在上圖中表示當前資源 R 正在被 A 程序所佔用 由程序節點到資源節點的有向圖表示當前程序正在請求資源,並且該程序已經被阻塞,處於等待這個資源的狀態 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150516738-1296714253.png) 在上圖中,表示的含義是程序 B 正在請求資源 S 。Holt 認為,死鎖的描述應該如下 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150533753-253222341.png) 這是一個死鎖的過程,程序 C 等待資源 T 的釋放,資源 T 卻已經被程序 D 佔用,程序 D 等待請求佔用資源 U ,資源 U 卻已經被執行緒 C 佔用,從而形成環。 總結一點:**吃著碗裡的看著鍋裡的容易死鎖** 那麼如何避免死鎖呢?我們還是通過死鎖模型來聊一聊 假設有三個程序 (A、B、C) 和三個資源(R、S、T) 。三個程序對資源的請求和釋放序列如下圖所示 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150540791-945596287.png) 作業系統可以任意選擇一個非阻塞的程式執行,所以它可以決定執行 A 直到 A 完成工作;它可以執行 B 直到 B 完成工作;最後執行 C。 這樣的順序不會導致死鎖(因為不存在對資源的競爭),但是這種情況也完全沒有`並行性`。程序除了在請求和釋放資源外,還要做計算和輸入/輸出的工作。當程序按照順序執行時,在等待一個 I/O 時,另一個程序不能使用 CPU。所以,嚴格按照序列的順序執行並不是最優越的。另一方面,如果沒有程序在執行任何 I/O 操作,那麼最短路徑優先作業會優於輪轉排程,所以在這種情況下序列可能是最優越的 現在我們假設程序會執行計算和 I/O 操作,所以輪詢排程是一種合理的`排程演算法`。資源請求可能會按照下面這個順序進行 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150546471-1564230524.png) 下圖是針對上面這六個步驟的資源分配圖。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150557819-1050478519.png) >這裡需要注意一個問題,為什麼從資源出來的有向圖指向了程序卻表示**程序請求資源**呢?筆者剛開始看也有這個疑問,但是想了一下這個意思解釋為程序佔用資源比較合適,而程序的有向圖指向資源表示程序被阻塞的意思。 在上面的第四個步驟,程序 A 正在等待資源 S;第五個步驟中,程序 B 在等待資源 T;第六個步驟中,程序 C 在等待資源 R,因此產生了環路並導致了死鎖。 然而,作業系統並沒有規定一定按照某種特定的順序來執行這些程序。遇到一個可能會引起死鎖的執行緒後,作業系統可以乾脆不批准請求,並把程序掛起一直到安全狀態為止。比如上圖中,如果作業系統認為有死鎖的可能,它可以選擇不把資源 S 分配給 B ,這樣 B 被掛起。這樣的話作業系統會只執行 A 和 C,那麼資源的請求和釋放就會是下面的步驟 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150608563-568245727.png) 下圖是針對上面這六個步驟的資源分配圖。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150614357-1354648297.png) 在第六步執行完成後,可以發現並沒有產生死鎖,此時就可以把資源 S 分配給 B,因為 A 程序已經執行完畢,C 程序已經拿到了它想要的資源。程序 B 可以直接獲得資源 S,也可以等待程序 C 釋放資源 T 。 有四種處理死鎖的策略: * 忽略死鎖帶來的影響(驚呆了) * 檢測死鎖並回復死鎖,死鎖發生時對其進行檢測,一旦發生死鎖後,採取行動解決問題 * 通過仔細分配資源來避免死鎖 * 通過破壞死鎖產生的四個條件之一來避免死鎖 下面我們分別介紹一下這四種方法 ## 鴕鳥演算法 最簡單的解決辦法就是使用`鴕鳥演算法(ostrich algorithm)`,把頭埋在沙子裡,假裝問題根本沒有發生。每個人看待這個問題的反應都不同。數學家認為死鎖是不可接受的,必須通過有效的策略來防止死鎖的產生。工程師想要知道問題發生的頻次,系統因為其他原因崩潰的次數和死鎖帶來的嚴重後果。如果死鎖發生的頻次很低,而經常會由於硬體故障、編譯器錯誤等其他作業系統問題導致系統崩潰,那麼大多數工程師不會修復死鎖。 ## 死鎖檢測和恢復 第二種技術是死鎖的檢測和恢復。這種解決方式不會嘗試去阻止死鎖的出現。相反,這種解決方案會希望死鎖儘可能的出現,在監測到死鎖出現後,對其進行恢復。下面我們就來探討一下死鎖的檢測和恢復的幾種方式 ### 每種型別一個資源的死鎖檢測方式 每種資源型別都有一個資源是什麼意思?我們經常提到的印表機就是這樣的,資源只有印表機,但是裝置都不會超過一個。 可以通過構造一張資源分配表來檢測這種錯誤,比如我們上面提到的 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150625293-1787589853.png) 的演算法來檢測從 P1 到 Pn 這 n 個程序中的死鎖。假設資源型別為 m,E1 代表資源型別1,E2 表示資源型別 2 ,Ei 代表資源型別 i (1 <= i <= m)。E 表示的是 `現有資源向量(existing resource vector)`,代表每種已存在的資源總數。 現在我們就需要構造兩個陣列:C 表示的是`當前分配矩陣(current allocation matrix)` ,R 表示的是 `請求矩陣(request matrix)`。Ci 表示的是 Pi 持有每一種型別資源的資源數。所以,Cij 表示 Pi 持有資源 j 的數量。Rij 表示 Pi 所需要獲得的資源 j 的數量 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150632822-407851303.png) 一般來說,已分配資源 j 的數量加起來再和所有可供使用的資源數相加 = 該類資源的總數。 死鎖的檢測就是基於向量的比較。每個程序起初都是沒有被標記過的,演算法會開始對程序做標記,程序被標記後說明程序被執行了,不會進入死鎖,當演算法結束時,任何沒有被標記過的程序都會被判定為死鎖程序。 上面我們探討了兩種檢測死鎖的方式,那麼現在你知道怎麼檢測後,你何時去做死鎖檢測呢?一般來說,有兩個考量標準: * 每當有資源請求時就去檢測,這種方式會佔用昂貴的 CPU 時間。 * 每隔 k 分鐘檢測一次,或者當 CPU 使用率降低到某個標準下去檢測。考慮到 CPU 效率的原因,如果死鎖程序達到一定數量,就沒有多少程序可以執行,所以 CPU 會經常空閒。 ### 從死鎖中恢復 上面我們探討了如何檢測程序死鎖,我們最終的目的肯定是想讓程式能夠正常的執行下去,所以針對檢測出來的死鎖,我們要對其進行恢復,下面我們會探討幾種死鎖的恢復方式 #### 通過搶佔進行恢復 在某些情況下,可能會臨時將某個資源從它的持有者轉移到另一個程序。比如在不通知原程序的情況下,將某個資源從程序中強制取走給其他程序使用,使用完後又送回。這種恢復方式一般比較困難而且有些簡單粗暴,並不可取。 #### 通過回滾進行恢復 如果系統設計者和機器操作員知道有可能發生死鎖,那麼就可以定期檢查流程。程序的檢測點意味著程序的狀態可以被寫入到檔案以便後面進行恢復。檢測點不僅包含`儲存映像(memory image)`,還包含`資源狀態(resource state)`。一種更有效的解決方式是不要覆蓋原有的檢測點,而是每出現一個檢測點都要把它寫入到檔案中,這樣當程序執行時,就會有一系列的檢查點檔案被累積起來。 為了進行恢復,要從上一個較早的檢查點上開始,這樣所需要資源的程序會回滾到上一個時間點,在這個時間點上,死鎖程序還沒有獲取所需要的資源,可以在此時對其進行資源分配。 #### 殺死程序恢復 最簡單有效的解決方案是直接殺死一個死鎖程序。但是殺死一個程序可能照樣行不通,這時候就需要殺死別的資源進行恢復。 另外一種方式是選擇一個環外的程序作為犧牲品來釋放程序資源。 ## 死鎖避免 我們上面討論的是如何檢測出現死鎖和如何恢復死鎖,下面我們探討幾種規避死鎖的方式 ### 單個資源的銀行家演算法 銀行家演算法是 Dijkstra 在 1965 年提出的一種排程演算法,它本身是一種死鎖的排程演算法。它的模型是基於一個城鎮中的銀行家,銀行家向城鎮中的客戶承諾了一定數量的貸款額度。演算法要做的就是判斷請求是否會進入一種不安全的狀態。如果是,就拒絕請求,如果請求後系統是安全的,就接受該請求。 比如下面的例子,銀行家一共為所有城鎮居民提供了 15 單位個貸款額度,一個單位表示 1k 美元,如下所示 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150646933-1025059332.png) 城鎮居民都喜歡做生意,所以就會涉及到貸款,每個人能貸款的最大額度不一樣,在某一時刻,A/B/C/D 的貸款金額如下 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150652826-1061187177.png) 上面每個人的貸款總額加起來是 13,馬上接近 15,銀行家只能給 A 和 C 進行放貸,可以拖著 B 和 D、所以,可以讓 A 和 C 首先完成,釋放貸款額度,以此來滿足其他居民的貸款。這是一種`安全`的狀態。 如果每個人的請求導致總額會超過甚至接近 15 ,就會處於一種不安全的狀態,如下所示 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150659472-1900546766.png) 這樣,每個人還能貸款至少 2 個單位的額度,如果其中有一個人發起最大額度的貸款請求,就會使系統處於一種死鎖狀態。 >
這裡注意一點:不安全狀態並不一定引起死鎖,由於客戶不一定需要其最大的貸款額度,但是銀行家不敢抱著這種僥倖心理。 銀行家演算法就是對每個請求進行檢查,檢查是否請求會引起不安全狀態,如果不會引起,那麼就接受該請求;如果會引起,那麼就推遲該請求。 類似的,還有多個資源的銀行家演算法,讀者可以自行了解。 ## 破壞死鎖 死鎖本質上是無法避免的,因為它需要獲得未知的資源和請求,但是死鎖是滿足四個條件後才出現的,它們分別是 * 互斥 * 保持和等待 * 不可搶佔 * 迴圈等待 我們分別對這四個條件進行討論,按理說破壞其中的任意一個條件就能夠破壞死鎖 ### 破壞互斥條件 我們首先考慮的就是**破壞互斥使用條件**。如果資源不被一個程序獨佔,那麼死鎖肯定不會產生。如果兩個印表機同時使用一個資源會造成混亂,印表機的解決方式是使用 `假離線印表機(spooling printer)` ,這項技術可以允許多個程序同時產生輸出,在這種模型中,實際請求印表機的唯一程序是印表機守護程序,也稱為後臺程序。後臺程序不會請求其他資源。我們可以消除印表機的死鎖。 後臺程序通常被編寫為能夠輸出完整的檔案後才能列印,假如兩個程序都佔用了假離線空間的一半,而這兩個程序都沒有完成全部的輸出,就會導致死鎖。 因此,儘量做到儘可能少的程序可以請求資源。 ### 破壞保持等待的條件 第二種方式是如果我們能阻止持有資源的程序請求其他資源,我們就能夠消除死鎖。一種實現方式是讓所有的程序開始執行前請求全部的資源。如果所需的資源可用,程序會完成資源的分配並執行到結束。如果有任何一個資源處於頻繁分配的情況,那麼沒有分配到資源的程序就會等待。 很多程序**無法在執行完成前就知道到底需要多少資源**,如果知道的話,就可以使用銀行家演算法;還有一個問題是這樣**無法合理有效利用資源**。 還有一種方式是程序在請求其他資源時,先釋放所佔用的資源,然後再嘗試一次獲取全部的資源。 ### 破壞不可搶佔條件 破壞不可搶佔條件也是可以的。可以通過虛擬化的方式來避免這種情況。 ### 破壞迴圈等待條件 現在就剩最後一個條件了,迴圈等待條件可以通過多種方法來破壞。一種方式是制定一個標準,一個程序在任何時候只能使用一種資源。如果需要另外一種資源,必須釋放當前資源。對於需要將大檔案從磁帶複製到印表機的過程,此限制是不可接受的。 另一種方式是將所有的資源統一編號,如下圖所示 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150708893-1847941850.png) 程序可以在任何時間提出請求,但是所有的請求都必須按照資源的順序提出。如果按照此分配規則的話,那麼資源分配之間不會出現環。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150715349-332903085.png) 儘管通過這種方式來消除死鎖,但是編號的順序不可能讓每個程序都會接受。 ## 其他問題 下面我們來探討一下其他問題,包括 **通訊死鎖、活鎖是什麼、飢餓問題和兩階段加鎖** ### 兩階段加鎖 雖然很多情況下死鎖的避免和預防都能處理,但是效果並不好。隨著時間的推移,提出了很多優秀的演算法用來處理死鎖。例如在資料庫系統中,一個經常發生的操作是請求鎖住一些記錄,然後更新所有鎖定的記錄。當同時有多個程序執行時,就會有死鎖的風險。 一種解決方式是使用 `兩階段提交(two-phase locking)`。顧名思義分為兩個階段,一階段是程序嘗試一次鎖定它需要的所有記錄。如果成功後,才會開始第二階段,第二階段是執行更新並釋放鎖。第一階段並不做真正有意義的工作。 如果在第一階段某個程序所需要的記錄已經被加鎖,那麼該程序會釋放所有鎖定的記錄並重新開始第一階段。從某種意義上來說,這種方法類似於預先請求所有必需的資源或者是在進行一些不可逆的操作之前請求所有的資源。 不過在一般的應用場景中,兩階段加鎖的策略並不通用。如果一個程序缺少資源就會半途中斷並重新開始的方式是不可接受的。 ### 通訊死鎖 我們上面一直討論的是資源死鎖,資源死鎖是一種死鎖型別,但並不是唯一型別,還有通訊死鎖,也就是兩個或多個程序在傳送訊息時出現的死鎖。程序 A 給程序 B 發了一條訊息,然後程序 A 阻塞直到程序 B 返回響應。假設請求訊息丟失了,那麼程序 A 在一直等著回覆,程序 B 也會阻塞等待請求訊息到來,這時候就產生`死鎖`。 儘管會產生死鎖,但是這並不是一個資源死鎖,因為 A 並沒有佔據 B 的資源。事實上,通訊死鎖並沒有完全可見的資源。根據死鎖的定義來說:每個程序因為等待其他程序引起的事件而產生阻塞,這就是一種死鎖。相較於最常見的通訊死鎖,我們把上面這種情況稱為`通訊死鎖(communication deadlock)`。 通訊死鎖不能通過排程的方式來避免,但是可以使用通訊中一個非常重要的概念來避免:`超時(timeout)`。在通訊過程中,只要一個資訊被髮出後,傳送者就會啟動一個定時器,定時器會記錄訊息的超時時間,如果超時時間到了但是訊息還沒有返回,就會認為訊息已經丟失並重新發送,通過這種方式,可以避免通訊死鎖。 但是並非所有網路通訊發生的死鎖都是通訊死鎖,也存在資源死鎖,下面就是一個典型的資源死鎖。 當一個數據包從主機進入路由器時,會被放入一個緩衝區,然後再傳輸到另外一個路由器,再到另一個,以此類推直到目的地。緩衝區都是資源並且數量有限。如下圖所示,每個路由器都有 10 個緩衝區(實際上有很多)。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150727925-1109283314.png) 假如路由器 A 的所有資料需要傳送到 B ,B 的所有資料包需要傳送到 D,然後 D 的所有資料包需要傳送到 A 。沒有資料包可以移動,因為在另一端沒有緩衝區可用,這就是一個典型的資源死鎖。 ### 活鎖 你會發現一個很有意思的事情,死鎖就跟榆木腦袋一樣,不會轉彎。我看過古代的一則故事: ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150736116-225151148.png) 如果說死鎖很`痴情`的話,那麼`活鎖`用一則成語來表示就是 `弄巧成拙`。 某些情況下,當程序意識到它不能獲取所需要的下一個鎖時,就會嘗試禮貌的釋放已經獲得的鎖,然後等待非常短的時間再次嘗試獲取。可以想像一下這個場景:當兩個人在狹路相逢的時候,都想給對方讓路,相同的步調會導致雙方都無法前進。 現在假想有一對並行的程序用到了兩個資源。它們分別嘗試獲取另一個鎖失敗後,兩個程序都會釋放自己持有的鎖,再次進行嘗試,這個過程會一直進行重複。很明顯,這個過程中沒有程序阻塞,但是程序仍然不會向下執行,這種狀況我們稱之為 `活鎖(livelock)`。 ### 飢餓 與死鎖和活鎖的一個非常相似的問題是 `飢餓(starvvation)`。想象一下你什麼時候會餓?一段時間不吃東西是不是會餓?對於程序來講,最重要的就是資源,如果一段時間沒有獲得資源,那麼程序會產生飢餓,這些程序會永遠得不到服務。 我們假設印表機的分配方案是每次都會分配給最小檔案的程序,那麼要列印大檔案的程序會永遠得不到服務,導致程序飢餓,程序會無限制的推後,雖然它沒有阻塞。 ## 總結 死鎖是一類通用問題,任何作業系統都會產生死鎖。當每一組程序中的每個程序都因等待由該組的其他程序所佔有的資源而導致阻塞,死鎖就發生了。這種情況會使所有的程序都處於無限等待的狀態。 死鎖的檢測和避免可以通過安全和不安全狀態來判斷,其中一個檢測方式就是銀行家演算法;當然你也可以使用鴕鳥演算法對死鎖置之不理,但是你肯定會遭其反噬。 也可以在設計時通過系統結構的角度來避免死鎖,這樣能夠預防死鎖;也可以破壞死鎖的四個條件來破壞死鎖。資源死鎖並不是唯一性的死鎖,還有通訊間死鎖,可以設定適當的超時時間來完成。 活鎖和死鎖的問題有些相似,它們都是一種程序無法繼續向下執行的狀態。由於程序排程策略導致嘗試獲取程序的一方永遠無法獲得資源後,程序會導致飢餓的出現。 ## 尾聲 提出一個勘誤,已反饋給出版社 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150745565-452013371.png) ## 關於我 我自己現在寫了三本 PDF,讀者回復都非常不錯,現在免費分享出來,關注我的公眾號即可領取 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200628150755073-7749884