記憶體模型與原子操作

讀-改-寫操作(read-modify-write,fetch_add/compare_exchange)

/ RMW -> ar-em-DUB-uhl-yoo /

讀-改-寫是一個原子操作,它讀出目前的值、由它算出一個新值、再寫回去——全部作為一個不可分割、沒有其他執行緒能把它拆開的單一步驟。這嚴格強過單純的原子載入或單純的原子儲存,因為光靠那兩者無法安全表達「遞增」或「交換」:在你的載入與儲存之間,別的執行緒可能偷溜進來。RMW 把這個縫隙封死。

日常的 RMW 有:fetch_add/fetch_sub/fetch_or/fetch_and(讀出舊值、套用運算、存回結果,並原子地回傳「舊」值);exchange(原子地寫入新值並回傳舊值);以及皇冠上的明珠,compare_exchange。比較交換(CAS)接受一個期望值與一個欲設值:原子地,「若」該位置目前等於期望值,就存入欲設值並回報成功;否則把實際的目前值載入期望值並回報失敗。它有兩種形式——compare_exchange_strong 永不假性失敗,而 compare_exchange_weak 即使值相符也被允許失敗,這在 load-linked/store-conditional 硬體(ARM、POWER)上更便宜,因此是放在重試迴圈裡的正確選擇。

RMW 是幾乎每一種鎖與無鎖結構背後的原子肌肉:互斥鎖由 exchange 或 CAS 打造、參考計數由 fetch_add/fetch_sub、無鎖堆疊的 push 由一個 CAS 迴圈構成。每個 RMW 都帶自己的 memory_order(常是 acq_rel,讓它既消費先前的發布、又發布自己的結果)。這裡冒出一個銳利的危害:一個 CAS 可能因為值「看起來」沒變而成功,即使它其實被改過又改回去了——這就是 ABA 問題(在無鎖欄位完整處理)。

int expected = atomic_load(&top); int desired; do { desired = expected + 1; } while (!atomic_compare_exchange_weak(&top, &expected, desired)); /* 失敗時 'expected' 會自動被刷新為目前值,所以我們直接重試 */

CAS 重試迴圈:weak CAS 失敗時自動把 expected 重新載入,使 do/while 成為慣用寫法。

在迴圈裡用 compare_exchange_weak(它的假性失敗在那裡毫無代價),一次性檢查才用 compare_exchange_strong;且絕不要把「CAS 成功了」讀成「中間沒人碰過它」——那正是 ABA 陷阱。

又称
RMWatomic CAS讀改寫操作