死結

鎖定排序(lock ordering)

想像一間工作坊,牆上貼著一條規則:「永遠依此順序拿工具——先手套、再護目鏡、最後電鑽——而且持有較高順位的工具時,絕不去抓較低順位的。」只要人人遵守,兩名工人就絕不會落得各抓著對方需要的一件工具,因為兩人都以同樣的次序去拿。鎖定排序正是把這套紀律用在並行程式的鎖上,也是對抗死結最實用的一道防線。

這個想法是打破循環等待條件在程式碼層級的具體形式。你為每把鎖在一個全域順序中指定固定位置(常常就是一個編號,或一份有文件的階層),並訂下嚴格規則:任何要取得多把鎖的執行緒,都必須依遞增順序取得。因為循環等待需要某個執行緒在持有較高順位的鎖時去請求較低順位的,而規則正好禁止這件事,於是等待的環永遠無法形成。當程式碼確實無法遵守該順序時,退路是採用「嘗試取鎖並退避」的協定:以非阻塞方式嘗試那把違序的鎖,若失敗就釋放一切、重新開始。

誠實的告誡決定了這個技術的成敗。順序必須是全域的、且處處被遵守——只要有一個函式、程式庫或回呼以錯誤順序取鎖,就可能重新引入死結。當鎖是動態建立的、或藏在抽象層之後時,要定義一個乾淨的全序可能很難;而穿過層層程式碼的巢狀上鎖,會讓真正的取鎖順序難以看清。工具有幫助:執行期的鎖序檢查器(如 Linux 核心的 lockdep)會監看每次取鎖,並在違反既定順序真正造成卡死之前就把它標記出來。

transfer(from, to):先鎖 id 較小的帳戶,再鎖較大的。於是 transfer(A, B) 與 transfer(B, A) 都會在 max(A, B) 之前先鎖 min(A, B),絕不會各自持有對方需要的一把鎖——沒有循環等待、沒有死結。

一致的全域取鎖順序,使經典的雙鎖死結不可能發生。

鎖定排序只有在全域且一致地施行時才有效——只要有一條路徑違序取鎖就會重新引入死結。動態或隱藏的鎖使乾淨的全序難以建立;像 lockdep 這類執行期檢查器有助於攔下違規。

又稱
lock hierarchylock-acquisition order鎖階層取鎖順序