死結處理方法與死結預防 (Methods for Handling Deadlocks and Deadlock Prevention)
本系列文章內容參考自經典教材 Operating System Concepts, 10th Edition (Silberschatz, Galvin, Gagne)。本文對應章節:Section 8.4–8.5 Methods for Handling Deadlocks / Deadlock Prevention。
前一節建立了分析死結的兩個核心工具:四個必要條件(互斥、持有並等待、不可搶占、循環等待)和資源分配圖(Resource-Allocation Graph)。知道死結在什麼條件下形成、也能在圖形上識別它之後,系統設計者面臨一個根本性的問題:對於死結,究竟應該採取什麼立場?本節回答這個問題,並深入探討其中一條路線的具體實作。
8.4 死結處理方法 (Methods for Handling Deadlocks)
三種根本策略
面對死結問題,系統設計者在概念上有三條路可以走:
- 無視死結:完全忽略這個問題,假裝死結不存在。
- 預防或 迴避死結:透過協定確保系統永遠不會進入死結狀態。
- 偵測並從死結中復原:允許系統進入死結狀態,偵測後再處理。
以下圖示呈現三種策略的選擇路徑,以及每條路線向下展開的方法:
這三條路線並非互斥,同一系統可以對不同種類的資源採用不同策略。例如,核心同步物件(mutex lock、semaphore)採取預防策略,而記憶體分配採用偵測策略。後續章節(8.5 至 8.8)會逐一深入每條路線的具體演算法。
策略一:無視死結(Ostrich Algorithm)
最直覺的問題是:忽略死結怎麼可能是一個合理的設計決策?
理解這個策略的關鍵在於成本效益分析。偵測死結與從死結中復原的機制本身有相當的系統開銷,但在許多通用系統中,死結的發生極為罕見,例如每個月才出現一次。如果為了應對這種低頻率事件而持續付出效能代價,從工程角度看並不合算。
此外還有一個務實的理由:死結並不是系統需要手動干預的唯一活躍性失敗(Liveness Failure)。即使沒有死結,系統仍然可能因為優先級最高的即時執行緒(Real-time Thread)長時間佔用 CPU 而不歸還,讓其他執行緒永遠無法執行;這種情況同樣需要手動重啟。換句話說,手動恢復機制本來就必須存在,死結只是可以附帶納入這個機制處理的情況之一。
當沒有死結偵測演算法時,未被偵測的死結會造成系統效能持續惡化:被死結鎖住的執行緒持有資源卻無法推進,而越來越多請求同一資源的執行緒也陸續進入死結狀態。最終,系統停止回應,必須人工重啟。這個代價在低頻率死結的場景下,往往比引入複雜偵測機制的代價更低。
因此,Linux 和 Windows 等大多數通用作業系統採用策略一:不主動偵測或預防死結,由核心與應用程式開發者自行負責避免死結,必要時透過其他活躍性恢復機制處理。
這個策略在學界常被戲稱為 Ostrich Algorithm(鴕鳥演算法):把頭埋進沙裡,假裝問題不存在。雖然名字帶著諷刺意味,但在工程上它代表一個刻意的取捨決定,而非真的無知。
策略二:預防 vs. 迴避的差異
策略二包含兩個方向,兩者都能讓系統永遠不進入死結狀態,但思路截然不同:
死結預防(Deadlock Prevention) 是結構性約束。系統在設計層面施加限制,使得四個必要條件中的至少一個永遠無法成立。這些限制在系統設計時決定,在執行期間始終有效,不需要執行期的動態判斷。代價是限制了程式可以請求資源的方式,可能降低資源使用率或增加設計複雜度。
死結迴避(Deadlock Avoidance) 是動態決策。OS 在執行期間,從每個執行緒預先獲取關於未來資源請求的額外資訊,並在每一次資源請求發生時,根據當前系統狀態判斷是否授予請求或讓執行緒等待。能迴避死結,但需要執行緒提前聲明最大資源需求,且每次請求都需要執行額外的演算法。詳見 Section 8.6。
策略三:偵測並復原
若系統不採用預防或迴避,死結就可能發生。此時系統需要兩個配套機制:
- 死結偵測演算法(Deadlock Detection Algorithm):定期檢查系統狀態,判斷是否陷入死結。
- 死結復原演算法(Deadlock Recovery Algorithm):確認死結後,採取措施打破等待循環,讓系統恢復正常。
資料庫系統廣泛採用這個策略,因為資料庫交易(Transaction)天生有「偵測到問題後可以回滾(Rollback)」的語義,復原成本相對可控。詳見 Section 8.7(偵測)和 Section 8.8(復原)。
8.5 死結預防 (Deadlock Prevention)
死結預防的核心邏輯很直接:若能確保四個必要條件中的