多核心、一致性與執行緒層級平行

比較並交換(compare-and-swap)

/ C-A-S /

假設兩個人都想搶桌邊最後一個座位,規則是:只有在它還空著時才坐。危險在於兩人都檢查「空嗎?」、都看到「是」、都坐下——撞車了。你要的是一個不可分割的單一動作:「若這座位還空著就坐;否則告訴我它已被佔。」沒有人能在檢查和坐下之間溜進來。比較並交換(compare-and-swap, CAS)正是對一個記憶體位置的這種不可分割動作:在一個原子步驟裡,它檢查某位置是否仍持有預期的值,僅當如此時才寫入一個新值。

它的簽名讀作 CAS(address, expected, new)。原子地——沒有別的核心能交錯進來——它做:讀出 address 處的值;若它等於 expected,就把 new 存進去並回報成功;否則什麼都不改並回報失敗(通常回傳它實際找到的值)。硬體保證整個讀-比較-寫作為一個不可中斷的單元發生。這是那個主力的原子讀-改-寫原語,也是你不靠鎖就安全更新共享資料的方法:讀出當前值、算出新值、再用 CAS 把它寫入——若 CAS 因另一個核心先改了值而失敗,就用新鮮的值迴圈重試。

比較並交換是無鎖程式設計的基石:許多執行緒並行更新的計數器、堆疊、佇列、參考計數器,都建立在 CAS 重試迴圈上。它強大,卻不是免費的午餐。在激烈競爭下,許多執行緒不斷失敗、重試,浪費工作;而 CAS 易受微妙的 ABA 問題所害——一個值從 A 變 B 再變回 A,於是 CAS 看到預期的 A 而成功,儘管底下的世界已經動過了。它的修法(版本計數器,或載入連結/儲存條件這個替代方案)正是無鎖程式碼出了名地難寫對的部分原因。

對共享計數器的無鎖遞增:迴圈 { old = counter;new = old + 1;} 直到 CAS(&counter, old, new) 成功。若另一個執行緒在讀取與 CAS 之間把計數器加大了,預期的 old 就不再相符、CAS 失敗,於是我們用新的當前值重試——遞增因此絕不被丟失,且不需鎖。

CAS 把讀-改-寫變成一個原子步驟,所以失敗的交換只要重試——這是無鎖計數器與堆疊的核心技巧。

CAS 可能在一次 ABA 變化中悄悄成功(值從 A 變 B 再變回 A,看起來沒變)。無鎖程式碼必須防範此事,例如用版本標記,或用能偵測任何插入寫入的載入連結/儲存條件。

又稱
CAScompare-and-exchange比較交換CAS 原子操作