同步

活結與飢餓

兩個人在狹窄走廊裡迎面相遇。各自禮貌地往旁邊讓——但兩人都選了同一邊,接著又都改往另一邊,然後又換回來,永遠互相鏡像、永遠擦不過去。他們不像死結那樣僵住;他們非常忙,只是毫無進展。這就是活結:執行緒不斷地為了回應彼此而改變狀態、積極地運轉著,卻沒有一個能完成它的任務。

活結通常源自天真的死結避免:一個執行緒拿了一把鎖、拿不到第二把、放開第一把再重試——若兩個執行緒完美同步地這麼做,它們就無止盡地搶-失敗-放開-搶-失敗-放開,誰也贏不了。解法是打破對稱,通常用隨機化退避(重試前隨機等一小段時間),讓執行緒不再齊步前進。飢餓是相關卻不同的苦難:某個特定執行緒持續被拒於它所需的資源之外,不是因為有環,而是因為別人老是被排在它前面選中。在嚴格優先權排程下,低優先權執行緒可能在高優先權執行緒不斷運轉時餓死;在偏好讀者的讀寫鎖下,寫者可能在讀者源源而來時餓死。飢餓的解藥是公平——一套保證每個等待者最終都輪得到的排程或上鎖策略(例如一個 FIFO 的等待者佇列)。

這三種故障模式構成一個值得精確區分的家族。死結:執行緒永遠卡住、什麼都不做(無進展、無活動)。活結:執行緒永遠忙碌、卻一事無成(無進展、大量活動)。飢餓:系統整體有進展,但某個倒楣的執行緒被無限期晾在一旁。指認出你遇到的是哪一種很重要,因為解藥各異:死結要鎖排序、活結要隨機化退避、飢餓要公平保證。

活結:兩個執行緒都做 do { lock(m1); if (!try_lock(m2)) { unlock(m1); continue; } work(); }。齊步時它們永遠地放開又重試。在 continue 前加一段隨機睡眠就能打破對稱、讓一方勝出。

活結很忙卻卡住;隨機化退避打破齊步。飢餓則需要一套公平策略。

活結看起來「活著」——CPU 很忙、程式似乎有反應——這讓人容易把它誤認成進展緩慢,而非毫無進展;該盯的是一個真實的計數器,而非只看 CPU 使用率。飢餓不是死結:系統照常運作,所以它可以躲很久,直到有人察覺某個執行緒永遠跑不完。

又稱
livelockstarvationlockout活鎖餓死