持有並等待(hold and wait)
想像有人做三明治,先抓了麵包,然後站在冰箱前等別人用完起司——而且還一直抓著麵包,害別人連烤土司都做不了。他同時「持有」一樣東西又「等待」另一樣。這種習慣在電腦裡就是持有並等待條件:行程在被阻塞、等著取得更多資源時,仍然抓著它已經擁有的資源不放。
精確地說,持有並等待是死結四個必要條件中的第二個。當行程被允許在仍持有先前配置給它的資源的同時去請求新資源時,這個條件就成立。正是它讓等待鏈得以形成:因為每個行程都不肯交出手上的東西,即使它正閒著發呆,它所持有的東西對其他所有人仍是不可用的。你可以用一個禁止「部分持有」的協定來否定這個條件。最簡單的是一次取得全部(all-at-once)的配置:行程必須在開始前,以一個不可分割的步驟一次請求它將會用到的所有資源,要嘛全部拿到、要嘛一個都不給。如此它就絕不會在等其他資源時還持有某些資源。
否定持有並等待確實能預防死結,但坦白說代價不小。一次取得全部的配置迫使行程在真正使用前老早就把資源抓在手裡,於是使用率下降(開機時就預訂的印表機可能閒置一小時)。它也可能造成飢餓:需要好幾個熱門資源的行程,可能因為它們很少同時全部空閒而無限期等待。另一種替代方案——再次請求前先釋放一切——在行程需要鎖住某些狀態時就很彆扭。這就是為什麼預防持有並等待雖然可行,卻很少成為通用系統採用的解方。
易死結:lock(A); ...; lock(B)。否定持有並等待(一次取得全部):lock_all(A, B),它會阻塞直到 A 與 B 都空閒,再一起授予,於是行程絕不會在只是等待 B 時還持有 A。
一次取一個資源會造成持有並等待;一次全部取得則否定它。
一次取得全部的配置確實能預防死結,但會浪費資源(早在使用前就被持有),且當好幾個熱門資源很少同時空閒時可能造成飢餓。