CAS 重試迴圈(CAS retry loop)
單一的 CAS 太小,本身做不了什麼有趣的事——它要嘛裝上你的更新,要嘛拒絕它。CAS 重試迴圈是把這個一次性原語變成完整原子操作的標準模式。它是幾乎每個無鎖演算法的心跳,一旦你認得它的形狀,就會處處看見它。
形狀永遠是同樣的三步,重複到成功為止。一:讀取目前共享值的快照(叫它 old)。二:在你自己私有的暫存器裡,根據 old 算出你想要的新值。三:把該位置從 old 用 CAS 換成 new。若 CAS 成功,表示你計算時底下沒有改變,你的更新有效,完成。若失敗,表示這段期間有別的執行緒改了值,於是你的快照已過時、你算出的新值建立在錯誤前提上——你只要重新讀取最新值,再把整件事重來一遍。這就是樂觀並行(optimistic concurrency):你假設沒有衝突、把工作做完,唯有真的發生衝突時才付出一次重試的代價。
兩件要誠實理解的事。第一,這個迴圈是無鎖、但不是等待無關:一個執行緒可能一次又一次輸掉競賽,而其他人持續贏,因此它的重試次數沒有上界——這正是兩種進展類別之間的界線。第二,第二步必須是純粹、無副作用的計算,因為在某一次嘗試生效之前,它可能跑很多次;你絕不能在迴圈本體內做任何可見的動作(印出、釋放記憶體、送訊息),只能放在 CAS 成功之後的成功分支裡。
/* 無鎖地把 f() 原子套用到一個共享值上。 */ int old, neu; do { old = atomic_load(&shared); /* 1:取快照 */ neu = f(old); /* 2:純計算,可能跑很多次 */ } while (!CAS(&shared, old, neu));/* 3:唯有未變才裝上,否則重試 */
讀取、計算、CAS、失敗就重試:樂觀無鎖更新的通用骨架。
迴圈本體必須純粹且可重跑,因為在某次 CAS 成功前它可能執行很多次。把不可逆的工作(釋放、I/O)放進迴圈內、而非放在成功的 CAS 之後,是經典的錯誤。