記憶體模型與原子操作

無資料競爭(data-race-free / DRF)保證

/ DRF -> dee-ar-EFF /

無資料競爭保證是記憶體模型開給你的條件,而且很慷慨:「寫一個沒有資料競爭的程式,我就讓你彷彿它跑在簡單的循序一致性下那樣去推理它——單一全域交錯、沒有奇怪的重排要操心。」弱序那一切嚇人的複雜,只有當你刻意去用 relaxed 原子時才要緊;對於普通且正確同步的程式,DRF 把你的直覺還給你。

先講定義。兩個記憶體存取若觸碰同一位置且至少一個是寫入,就「衝突」。「資料競爭」是來自不同執行緒的兩個衝突存取,它們未被 happens-before 排序,且其中至少一個是非原子的普通存取。一個程式若不存在任何執行能呈現這樣一對,就是無資料競爭的。你達成它的方式,是守護每一個共享可變位置——用互斥鎖、或把它變成原子、或在每一對衝突存取之間建立 release/acquire 的 happens-before 邊。

現在講那條硬性、不容商量的規則:在 C、C++ 以及 Rust 的 safe/unsafe 之分裡,資料競爭都是「未定義行為」。這是人們最低估的部分。未定義行為「不」代表「你會拿到一個亂掉的值」或「它不可預測但有界」。它代表標準不施加任何要求,於是最佳化器有權「假設」你的程式沒有資料競爭,並可能刪掉程式碼、合併載入、或產生看似不可能的輸出——甚至完全不當機。「我測試時是好的」對一個有競爭的程式什麼也證明不了。(safe Rust 是特例:它的借用檢查器在編譯期靜態地「防止」資料競爭,這正是它的招牌保證——但 unsafe 區塊與 FFI 仍可能製造出競爭。)

int flag = 0; /* 普通 int,不是原子 */ T1:flag = 1; /* 寫 */ T2:while (!flag) {} /* 讀 */ <- 衝突、未排序、非原子 = 資料競爭 = UB 編譯器可以合法地把 !flag 提到迴圈外、永遠自旋,或乾脆把迴圈最佳化掉。

被兩個執行緒自旋的非原子旗標是未定義行為——把 flag 改成 _Atomic 就修好了。

資料競爭是「未定義行為」,不只是拿到錯值:編譯器可假設它不可能發生而對整段區域產生錯誤的編譯結果,所以一個「測試從沒現形」的競爭,可能在任何最佳化或平台更換後引爆。

又称
DRF-SCSC-for-DRF無資料競爭保證