死結與 Coffman 條件
/ KOF-muhn /
兩輛車在單線橋上相遇,各自擋住對方又都不肯倒退。誰都動不了;兩邊永遠等下去。那個僵住的對峙就是死結:一組執行緒各自等待著「組裡另一個執行緒正持有」的資源,於是沒有一個能再有進展。經典情形是:執行緒 A 持著鎖 1、想要鎖 2,而執行緒 B 持著鎖 2、想要鎖 1——彼此都禮貌地等對方放手,而誰都不會。
死結只有在四個條件(以 E. G. Coffman 命名)同時成立時才會發生。互斥:至少有一份資源以不可共享的方式被持有(一把鎖被一個執行緒持著)。持有並等待:執行緒在等著取得更多資源時,仍保留它已持有的資源。不可搶佔:資源無法被強行從持有它的執行緒手中奪走——必須自願釋放。循環等待:存在一個執行緒的環,每一個都在等環中下一個所持有的資源。這四個都是必要的;只要打破任何一個,死結就變得不可能。你可以把「誰持有什麼、誰等待什麼」建成一張圖(資源配置圖或等待圖)並找其中的環,藉此偵測死結;有些執行期與工具正是這麼做的。
死結是最令人懼怕的並行錯誤之一,因為它常與時序相關:程式跑一千次都好端端的,然後就在兩個執行緒碰巧以相反順序搶到它們兩把鎖的那一次,永遠卡住。它不當機、不留日誌——它就只是停住,這正是它診斷起來令人痛苦的原因。好消息是,那四個條件同時也是你的工具箱:各種預防策略各自針對其中一個,最常見的是攻擊循環等待——施加一條全域鎖排序規則(見死結預防)。
執行緒 A:lock(m1); lock(m2); ... 執行緒 B:lock(m2); lock(m1); ... ——若同一瞬間 A 持著 m1、B 持著 m2,接著各自就會永遠阻塞,等著對方持有的那把鎖。這就是循環等待。
相反的鎖順序在等待圖裡造出一個環;四個 Coffman 條件全都成立。
死結不會當機、也不報錯——那些執行緒就只是永遠停住,所以它常看起來像「卡死」。確認它通常得靠執行緒消毒器、或一份執行緒傾印(顯示每個執行緒都阻塞在別人持有的鎖上);盯著日誌沒用,因為什麼都沒被記下來。