什麼是 Deadlock#
Deadlock(死結)是多個執行緒互相等待對方持有的資源,導致所有執行緒都無法繼續執行的狀態。經典場景:兩個執行緒各需要鎖定兩個共享資料結構,若其中一個鎖了 A 等 B,另一個鎖了 B 等 A,就會陷入死結。
Bryan Cantrill 和 Jeff Bonwick 在 ACM Queue 的文章 “Real-world Concurrency” 中指出,deadlock 其實是最容易分析的並行錯誤之一——因為系統是凍結的,你可以從容地取得快照來分析。
事後分析的步驟#
C/C++ 程式(使用 gdb)#
- 取得 core dump:找到凍結程式的 PID,用
kill -QUIT產生 core dump - 載入 gdb:指定執行檔和 core dump 檔案
gdb deadlock core - 列出執行緒:用
info threads查看所有執行緒的狀態 - 逐一檢查:用
thread N切換到等待中的執行緒,再用backtrace查看 stack frame - 定位程式碼:從 stack trace 中找到你的程式碼(而非系統函式庫)中嘗試取得鎖的位置
Java 程式(使用 jstack)#
- Java 的
jstack工具更為方便,直接就能顯示 deadlock 資訊 - 用
jps找到 process ID,再執行jstack -l <PID> - jstack 會自動偵測並報告 deadlock,包括哪些執行緒在等待哪些 monitor,以及對應的程式碼位置
$ jps
5504 Deadlock
$ jstack -l 5504處理 Deadlock 的策略#
當確認系統可能發生 deadlock 後,需要了解系統設計中如何處理此問題:
- 忽略(ignoring)
- 偵測(detecting)
- 預防(preventing)
- 避免(avoiding)
常見的預防手法#
- 減少鎖的數量
- 避免 reentrancy(可重入性)
- 建立 lock hierarchy(鎖定階層):以 partial ordering 確保所有執行緒以相同順序取得鎖
- 使用 try-lock 而非 blocking lock,讓鎖定失敗時可以退回
最實用的預防方式是資源排序(partial ordering)——確保所有執行緒永遠以相同順序取得鎖。例如範例中只要讓
alice也先鎖m1再鎖m2,deadlock 就不會發生。
重點回顧#
- Deadlock 發生時系統是凍結的,可以透過 core dump 取得快照進行事後分析
- 使用 debugger 的
info threads和backtrace定位每個等待中執行緒嘗試取得鎖的程式碼位置 - Java 的 jstack 能自動偵測並報告 deadlock 及相關的 stack trace
- 確認 deadlock 後,透過資源排序、減少鎖數量或使用 try-lock 等方式預防