理解 C++ 內存模型是編寫正確高性能多線程程式的基石。本文從亂序執行的根源講起,經順序一致性模型,詳細闡述 C++11 引入的 memory_order 六大語義,重點剖析 relaxed、release-acquire、seq_cst 的實際含義與使用場景。
程式碼書寫順序 ≠ 實際執行順序,三大來源:
單線程語義保證:上述亂序在單線程下不改變可觀測結果(as-if-serial 語義),因此單線程程式無感知。
| 類型 | 示例 | 說明 |
|---|---|---|
| Store-Store | a=3; b=4; → b=4; a=3; | 兩次寫入順序交換 |
| Store-Load | a=3; load(b); → load(b); a=3; | 寫入後移到讀取之後 |
| Load-Load | load(a); load(b); → load(b); load(a); | 兩次讀取順序交換 |
| Load-Store | load(a); b=4; → b=4; load(a); | 讀取後移到寫入之後 |
mutex/lock 保護臨界區。Lamport 定義:所有線程的操作存在某種全局交錬順序,且每個線程內部順序與程式順序一致,所有線程觀測到的全局順序相同。
核心契約:無數據競爭的程式表現為順序一致性。程式員用同步原語(鎖、atomic)標記需同步的操作,編譯器/CPU 僅在這些點禁止違反 SC 的優化,其餘處保留優化自由度。
std::memory_order 六大語義enum memory_order {
memory_order_relaxed, // 無同步,僅保證原子性
memory_order_consume, // 數據依賴排序(較少用)
memory_order_acquire, // 讀屏障
memory_order_release, // 寫屏障
memory_order_acq_rel, // 讀寫屏障(RMW操作)
memory_order_seq_cst // 全局順序一致性(最強)
};
store(..., release),線程 B load(..., acquire)。// Thread A
a.store(3, relaxed);
b.store(4, relaxed);
m.store(5, release); // 同步點
// Thread B
m.load(acquire); // 同步點
assert(a.load(relaxed) == 3); // 必須可見
assert(b.load(relaxed) == 4); // 必須可見
seq_cst 操作形成單一全局順序,所有線程觀測一致。atomic 操作不指定語義時默認 seq_cst。// Thread A // Thread B
a.store(1, seq_cst); b.store(1, seq_cst);
// 全局順序要么 A先B 要么 B先A,不可同時觀測到 a=0,b=1 和 a=1,b=0
fetch_add、compare_exchange_strong 等原子 RMW 指令同時具備 acquire 與 release 語義,並充當全屏障。| 場景 | 建議語義 | 理由 |
|---|---|---|
| 引用計數、無關計數器 | relaxed | 僅需原子性,無同步需求 |
| 生產者-消費者標誌位 | release (生產) / acquire (消費) | 傳遞數據依賴,建立 Happens-Before |
| 雙檢查鎖、單例模式 | seq_cst | 需全局順序防止指令重排導致半初始化對象可見 |
| 無鎖隊列頭尾索引 | acq_rel (RMW) | CAS 循環需同時具備 acquire/release |
atomic + memory_order 為核心,提供從 relaxed 到 seq_cst 的漸進式同步強度。seq_cst 最安全但最慢;性能敏感處經驗證後可降級至 release/acquire 或 relaxed。