名稱空間
變體
操作

std::memory_order

來自 cppreference.com
< cpp‎ | atomic
 
 
併發支援庫
執行緒
(C++11)
(C++20)
this_thread 名稱空間
(C++11)
(C++11)
(C++11)
協同取消
互斥
(C++11)
通用鎖管理
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
條件變數
(C++11)
訊號量
門閂和屏障
(C++20)
(C++20)
期值
(C++11)
(C++11)
(C++11)
(C++11)
安全回收
(C++26)
危險指標
原子型別
(C++11)
(C++20)
原子型別的初始化
(C++11)(C++20 中已棄用)
(C++11)(C++20 中已棄用)
記憶體排序
memory_order
(C++11)
(C++11)(C++26 中已棄用)
原子操作的自由函式
原子標誌的自由函式
 
定義於標頭檔案 <atomic>
enum memory_order

{
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst

};
(C++11 起)
(C++20 前)
enum class memory_order : /* 未指定 */

{
    relaxed, consume, acquire, release, acq_rel, seq_cst
};
inline constexpr memory_order memory_order_relaxed = memory_order::relaxed;
inline constexpr memory_order memory_order_consume = memory_order::consume;
inline constexpr memory_order memory_order_acquire = memory_order::acquire;
inline constexpr memory_order memory_order_release = memory_order::release;
inline constexpr memory_order memory_order_acq_rel = memory_order::acq_rel;

inline constexpr memory_order memory_order_seq_cst = memory_order::seq_cst;
(C++20 起)

std::memory_order 指定了記憶體訪問,包括常規的非原子記憶體訪問,如何在原子操作周圍進行排序。在一個多核系統上,如果沒有約束,當多個執行緒同時讀寫多個變數時,一個執行緒觀察到的值變化的順序可能與另一個執行緒寫入的順序不同。事實上,變化的表觀順序甚至在多個讀取執行緒之間也可能不同。即使在單處理器系統上,由於記憶體模型允許的編譯器轉換,也可能發生一些類似的效果。

庫中所有原子操作的預設行為提供*順序一致性排序*(參見下面的討論)。該預設行為可能會影響效能,但庫的原子操作可以給定一個額外的 std::memory_order 引數來指定編譯器和處理器必須為該操作強制執行的精確約束,除了原子性之外。

目錄

[編輯] 常量

定義於標頭檔案 <atomic>
名稱 解釋
memory_order_relaxed 寬鬆操作:不施加任何同步或排序約束於其他讀寫,僅保證此操作的原子性(參見下方寬鬆排序)。
memory_order_consume
(C++26 中已棄用)
此記憶體順序的載入操作在受影響的記憶體位置執行*消費操作*:當前執行緒中依賴於當前載入的值的任何讀或寫不能在此載入之前重新排序。在其他執行緒中,釋放相同原子變數的資料依賴變數的寫入在當前執行緒中可見。在大多數平臺上,這僅影響編譯器最佳化(參見下方釋放-消費排序)。
memory_order_acquire 此記憶體順序的載入操作在受影響的記憶體位置執行*獲取操作*:當前執行緒中的任何讀或寫不能在此載入之前重新排序。在其他執行緒中,釋放相同原子變數的所有寫入在當前執行緒中可見(參見下方釋放-獲取排序)。
memory_order_release 此記憶體順序的儲存操作執行*釋放操作*:當前執行緒中的任何讀或寫不能在此儲存之後重新排序。當前執行緒中的所有寫入在獲取相同原子變數的其他執行緒中可見(參見下方釋放-獲取排序),並且攜帶依賴到原子變數的寫入在消費相同原子的其他執行緒中可見(參見下方釋放-消費排序)。
memory_order_acq_rel 此記憶體順序的讀-修改-寫操作既是*獲取操作*又是*釋放操作*。當前執行緒中的任何記憶體讀或寫不能在此載入之前或此儲存之後重新排序。在其他執行緒中,釋放相同原子變數的所有寫入在修改之前可見,並且該修改在獲取相同原子變數的其他執行緒中可見。
memory_order_seq_cst 此記憶體順序的載入操作執行*獲取操作*,儲存操作執行*釋放操作*,讀-修改-寫操作既執行*獲取操作*又執行*釋放操作*,此外還存在一個單一的總序,所有執行緒都以相同的順序觀察所有修改(參見下方順序一致性排序)。

[編輯] 正式描述

執行緒間同步和記憶體排序決定了表示式的*求值*和*副作用*如何在不同的執行執行緒之間排序。它們透過以下術語定義:

[編輯] 順序前 (Sequenced-before)

在同一執行緒內,求值 A 可能*順序前*求值 B,如求值順序中所述。

攜帶依賴 (Carries dependency)

在同一執行緒內,如果以下任一條件為真,則*順序前*求值 B 的求值 A 也可能*攜帶依賴*到 B(即,B 依賴於 A):

1) A 的值用作 B 的運算元,**除了**
a) 如果 B 是對 std::kill_dependency 的呼叫,
b) 如果 A 是內建 &&||?:, 運算子的左運算元。
2) A 寫入標量物件 M,B 從 M 讀取。
3) A 攜帶依賴到另一個求值 X,且 X 攜帶依賴到 B。
(直到 C++26)

[編輯] 修改順序

對任何特定原子變數的所有修改都按照一個特定於該原子變數的總順序發生。

所有原子操作都保證以下四個要求:

1) **寫-寫一致性**:如果修改某個原子 M 的求值 A(寫入)*先行發生*修改 M 的求值 B,那麼在 M 的*修改順序*中,A 出現在 B 之前。
2) **讀-讀一致性**:如果某個原子 M 的值計算 A(讀取)*先行發生*對 M 的值計算 B,並且 A 的值來自對 M 的寫入 X,那麼 B 的值要麼是 X 儲存的值,要麼是 M 上在 M 的*修改順序*中出現在 X 之後的副作用 Y 儲存的值。
3) **讀-寫一致性**:如果某個原子 M 的值計算 A(讀取)*先行發生*對 M 的操作 B(寫入),那麼 A 的值來自在 M 的*修改順序*中出現在 B 之前的副作用(寫入)X。
4) **寫-讀一致性**:如果對原子物件 M 的副作用(寫入)X *先行發生*對 M 的值計算(讀取)B,那麼求值 B 應從 X 或從在 M 的修改順序中跟隨 X 的副作用 Y 獲取其值。

[編輯] 釋放序列

在對原子物件 M 執行*釋放操作* A 之後,M 的修改順序中由以下兩部分組成的最長連續子序列:

1) 由執行 A 的同一執行緒執行的寫入。
(C++20 前)
2) 任何執行緒對 M 執行的原子讀-修改-寫操作。

被稱為*以 A 為頭的釋放序列*。

[編輯] 同步於 (Synchronizes with)

如果執行緒 A 中的原子儲存是*釋放操作*,執行緒 B 從同一變數的原子載入是*獲取操作*,並且執行緒 B 中的載入讀取了執行緒 A 中儲存的值,那麼執行緒 A 中的儲存*同步於*執行緒 B 中的載入。

此外,一些庫呼叫可能被定義為*同步於*其他執行緒上的其他庫呼叫。

依賴有序前 (Dependency-ordered before)

線上程之間,如果以下任一條件為真,則求值 A *依賴有序前*求值 B:

1) A 在某個原子 M 上執行*釋放操作*,並且在不同的執行緒中,B 在相同的原子 M 上執行*消費操作*,且 B 讀取了由以 A 為頭的釋放序列的任何部分寫入的(直到 C++20)值。
2) A *依賴有序前* X,且 X *攜帶依賴*到 B。
(直到 C++26)

[編輯] 執行緒間先行發生 (Inter-thread happens-before)

線上程之間,如果以下任一條件為真,則求值 A *執行緒間先行發生*求值 B:

1) A *同步於* B。
2) A *依賴有序前* B。
3) A *同步於*某個求值 X,且 X *順序前* B。
4) A *順序前*某個求值 X,且 X *執行緒間先行發生* B。
5) A *執行緒間先行發生*某個求值 X,且 X *執行緒間先行發生* B。


先行發生 (Happens-before)

無論執行緒如何,如果以下任一條件為真,則求值 A *先行發生*求值 B:

1) A *順序前* B。
2) A *執行緒間先行發生* B。

實現要求透過引入額外的同步(僅當涉及消費操作時才可能需要,參見 Batty 等人)來確保*先行發生*關係是無環的。

如果一個求值修改了一個記憶體位置,而另一個讀取或修改了相同的記憶體位置,並且如果至少一個求值不是原子操作,則程式的行為是未定義的(程式存在資料競爭),除非在這些兩個求值之間存在*先行發生*關係。

簡單先行發生 (Simply happens-before)

無論執行緒如何,如果以下任一條件為真,則求值 A *簡單先行發生*求值 B:

1) A *順序前* B。
2) A *同步於* B。
3) A *簡單先行發生* X,且 X *簡單先行發生* B。

注意:如果沒有消費操作,*簡單先行發生*和*先行發生*關係是相同的。

(C++20 起)
(直到 C++26)

先行發生 (Happens-before)

無論執行緒如何,如果以下任一條件為真,則求值 A *先行發生*求值 B:

1) A *順序前* B。
2) A *同步於* B。
3) A *先行發生* X,且 X *先行發生* B。
(C++26 起)

[編輯] 強先行發生 (Strongly happens-before)

無論執行緒如何,如果以下任一條件為真,則求值 A *強先行發生*求值 B:

1) A *順序前* B。
2) A *同步於* B。
3) A *強先行發生* X,且 X *強先行發生* B。
(C++20 前)
1) A *順序前* B。
2) A *同步於* B,且 A 和 B 都是順序一致的原子操作。
3) A *順序前* X,X 簡單(直到 C++26) *先行發生* Y,且 Y *順序前* B。
4) A *強先行發生* X,且 X *強先行發生* B。

注意:非正式地,如果 A *強先行發生* B,那麼在所有上下文中 A 都顯得在 B 之前求值。

注意:*強先行發生*不包括消費操作。

(直到 C++26)
(C++20 起)

[編輯] 可見副作用

對標量 M 的副作用 A(寫入)對於 M 的值計算 B(讀取)是*可見的*,如果以下兩個條件都為真:

1) A *先行發生* B。
2) 不存在對 M 的其他副作用 X,使得 A *先行發生* X 且 X *先行發生* B。

如果副作用 A 對於值計算 B 是可見的,那麼在*修改順序*中,對 M 的副作用的最長連續子集(其中 B 不*先行發生*它)被稱為*可見副作用序列*(M 的值,由 B 確定,將是這些副作用之一儲存的值)。

注意:執行緒間同步歸結為防止資料競爭(透過建立先行發生關係)和定義在什麼條件下什麼副作用變得可見。

[編輯] 消費操作

使用 memory_order_consume 或更強記憶體順序的原子載入是消費操作。請注意,std::atomic_thread_fence 施加了比消費操作更強的同步要求。

[編輯] 獲取操作

使用 memory_order_acquire 或更強記憶體順序的原子載入是獲取操作。Mutex 上的 lock() 操作也是獲取操作。請注意,std::atomic_thread_fence 施加了比獲取操作更強的同步要求。

[編輯] 釋放操作

使用 memory_order_release 或更強記憶體順序的原子儲存是釋放操作。Mutex 上的 unlock() 操作也是釋放操作。請注意,std::atomic_thread_fence 施加了比釋放操作更強的同步要求。

[編輯] 解釋

[編輯] 寬鬆排序

標記為 memory_order_relaxed 的原子操作不是同步操作;它們不會在併發記憶體訪問之間施加順序。它們僅保證原子性和修改順序一致性。

例如,如果 xy 最初為零,

// Thread 1:
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// Thread 2:
r2 = x.load(std::memory_order_relaxed); // C 
y.store(42, std::memory_order_relaxed); // D

允許產生 r1 == r2 == 42,因為儘管執行緒 1 中的 A *順序前* B,且執行緒 2 中的 C *順序前* D,但沒有什麼能阻止 D 在 y 的修改順序中出現在 A 之前,以及 B 在 x 的修改順序中出現在 C 之前。D 對 y 的副作用可能對執行緒 1 中的載入 A 可見,而 B 對 x 的副作用可能對執行緒 2 中的載入 C 可見。特別地,這可能發生線上程 2 中 D 在 C 之前完成的情況下,無論是由於編譯器重排還是執行時。

即使在寬鬆的記憶體模型下,也不允許“憑空產生”的值迴圈依賴於其自身的計算,例如,如果 xy 最初為零,

// Thread 1:
r1 = y.load(std::memory_order_relaxed);
if (r1 == 42)
    x.store(r1, std::memory_order_relaxed);
// Thread 2:
r2 = x.load(std::memory_order_relaxed);
if (r2 == 42)
    y.store(42, std::memory_order_relaxed);

不允許產生 r1 == r2 == 42,因為只有當對 x 的儲存儲存 42 時,對 y 的儲存 42 才可能,這又迴圈依賴於對 y 的儲存儲存 42。注意,直到 C++14,這在技術上是規範允許的,但不建議實現者這樣做。

(C++14 起)

寬鬆記憶體排序的典型用途是遞增計數器,例如 std::shared_ptr 的引用計數器,因為這隻需要原子性,而不需要排序或同步(請注意,遞減 std::shared_ptr 計數器需要與解構函式進行獲取-釋放同步)。

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>
 
std::atomic<int> cnt = {0};
 
void f()
{
    for (int n = 0; n < 1000; ++n)
        cnt.fetch_add(1, std::memory_order_relaxed);
}
 
int main()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n)
        v.emplace_back(f);
    for (auto& t : v)
        t.join();
    std::cout << "Final counter value is " << cnt << '\n';
}

輸出

Final counter value is 10000

[編輯] 釋放-獲取排序

如果執行緒 A 中的原子儲存標記為 memory_order_release,執行緒 B 從同一變數的原子載入標記為 memory_order_acquire,並且執行緒 B 中的載入讀取了執行緒 A 中儲存的值,那麼執行緒 A 中的儲存*同步於*執行緒 B 中的載入。

從執行緒 A 的角度來看,所有*先行發生*原子儲存的記憶體寫入(包括非原子和寬鬆原子)都成為執行緒 B 中的*可見副作用*。也就是說,一旦原子載入完成,執行緒 B 保證能看到執行緒 A 寫入記憶體的所有內容。這個承諾僅在 B 實際返回 A 儲存的值,或釋放序列中稍後的值時才成立。

同步僅在*釋放*和*獲取*相同原子變數的執行緒之間建立。其他執行緒可能看到與一個或兩個同步執行緒不同的記憶體訪問順序。

在強有序系統(x86、SPARC TSO、IBM 大型機等)上,釋放-獲取排序對於大多數操作是自動的。無需為此同步模式發出額外的 CPU 指令;僅某些編譯器最佳化受到影響(例如,編譯器被禁止將非原子儲存移過原子儲存-釋放,或在原子載入-獲取之前執行非原子載入)。在弱有序系統(ARM、Itanium、PowerPC)上,使用特殊的 CPU 載入或記憶體屏障指令。

互斥鎖,例如 std::mutex原子自旋鎖,是釋放-獲取同步的一個例子:當執行緒 A 釋放鎖並由執行緒 B 獲取時,執行緒 A 上下文中的臨界區(在釋放之前)發生的一切都必須對執行相同臨界區的執行緒 B(在獲取之後)可見。

#include <atomic>
#include <cassert>
#include <string>
#include <thread>
 
std::atomic<std::string*> ptr;
int data;
 
void producer()
{
    std::string* p = new std::string("Hello");
    data = 42;
    ptr.store(p, std::memory_order_release);
}
 
void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_acquire)))
        ;
    assert(*p2 == "Hello"); // never fires
    assert(data == 42); // never fires
}
 
int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join(); t2.join();
}

以下示例演示了使用釋放序列在三個執行緒之間進行傳遞性釋放-獲取排序。

#include <atomic>
#include <cassert>
#include <thread>
#include <vector>
 
std::vector<int> data;
std::atomic<int> flag = {0};
 
void thread_1()
{
    data.push_back(42);
    flag.store(1, std::memory_order_release);
}
 
void thread_2()
{
    int expected = 1;
    // memory_order_relaxed is okay because this is an RMW,
    // and RMWs (with any ordering) following a release form a release sequence
    while (!flag.compare_exchange_strong(expected, 2, std::memory_order_relaxed))
    {
        expected = 1;
    }
}
 
void thread_3()
{
    while (flag.load(std::memory_order_acquire) < 2)
        ;
    // if we read the value 2 from the atomic flag, we see 42 in the vector
    assert(data.at(0) == 42); // will never fire
}
 
int main()
{
    std::thread a(thread_1);
    std::thread b(thread_2);
    std::thread c(thread_3);
    a.join(); b.join(); c.join();
}

[編輯] 釋放-消費排序

如果執行緒 A 中的原子儲存標記為 memory_order_release,執行緒 B 從同一變數的原子載入標記為 memory_order_consume,並且執行緒 B 中的載入讀取了執行緒 A 中儲存的值,那麼執行緒 A 中的儲存*依賴有序前*執行緒 B 中的載入。

從執行緒 A 的角度來看,所有*先行發生*原子儲存的記憶體寫入(非原子和寬鬆原子)都成為執行緒 B 中那些載入操作*攜帶依賴*的操作中的*可見副作用*,也就是說,一旦原子載入完成,執行緒 B 中使用從載入中獲取的值的運算子和函式就保證能看到執行緒 A 寫入記憶體的內容。

同步僅在*釋放*和*消費*相同原子變數的執行緒之間建立。其他執行緒可能看到與一個或兩個同步執行緒不同的記憶體訪問順序。

在除 DEC Alpha 之外的所有主流 CPU 上,依賴排序是自動的,無需為此同步模式發出額外的 CPU 指令,僅某些編譯器最佳化受到影響(例如,編譯器被禁止對涉及依賴鏈的物件執行推測性載入)。

此排序的典型用例涉及對很少寫入的併發資料結構(路由表、配置、安全策略、防火牆規則等)的讀取訪問,以及使用指標介導的釋出(即當生產者釋出一個指標,消費者可以透過該指標訪問資訊)的釋出-訂閱場景:無需使生產者寫入記憶體的所有其他內容對消費者可見(這在弱有序架構上可能是一個昂貴的操作)。這種場景的一個例子是 rcu_dereference

另請參閱 std::kill_dependency[[carries_dependency]] 以進行細粒度的依賴鏈控制。

請注意,目前(2015 年 2 月)沒有已知的生產編譯器跟蹤依賴鏈:消費操作被提升為獲取操作。

(直到 C++26)

釋放-消費排序的規範正在修訂中,暫時不鼓勵使用 memory_order_consume

(C++17 起)
(直到 C++26)

釋放-消費排序與釋放-獲取排序具有相同的效果,並且已被棄用。

(C++26 起)

此示例演示了指標介導釋出的依賴有序同步:整數資料透過資料依賴關係與字串指標不相關,因此其值在消費者中是未定義的。

#include <atomic>
#include <cassert>
#include <string>
#include <thread>
 
std::atomic<std::string*> ptr;
int data;
 
void producer()
{
    std::string* p = new std::string("Hello");
    data = 42;
    ptr.store(p, std::memory_order_release);
}
 
void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_consume)))
        ;
    assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr
    assert(data == 42); // may or may not fire: data does not carry dependency from ptr
}
 
int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join(); t2.join();
}


[編輯] 順序一致性排序

標記為 memory_order_seq_cst 的原子操作不僅以與釋放/獲取排序相同的方式對記憶體進行排序(在一個執行緒中*先行發生*儲存的所有內容在執行載入的執行緒中成為*可見副作用*),而且還建立了所有如此標記的原子操作的*單一總修改順序*。

形式上,

每個從原子變數 M 載入的 memory_order_seq_cst 操作 B,觀察到以下之一:

  • 在單一總序中出現在 B 之前的最後一次修改 M 的操作 A 的結果,
  • 或者,如果存在這樣的 A,B 可能觀察到對 M 的某種非 memory_order_seq_cst 且不*先行發生* A 的修改結果,
  • 或者,如果不存在這樣的 A,B 可能觀察到對 M 的某種不相關的非 memory_order_seq_cst 修改結果。

如果存在一個 memory_order_seq_cst std::atomic_thread_fence 操作 X *順序前* B,則 B 觀察到以下之一:

  • 在單一總序中出現在 X 之前的最後一次 memory_order_seq_cst 對 M 的修改,
  • 在 M 的修改順序中出現在稍後的一些不相關的對 M 的修改。

對於 M 上的原子操作 A 和 B,其中 A 寫入且 B 讀取 M 的值,如果存在兩個 memory_order_seq_cst std::atomic_thread_fence X 和 Y,並且如果 A *順序前* X,Y *順序前* B,並且 X 在單一總序中出現在 Y 之前,那麼 B 觀察到以下之一:

  • A 的效果,
  • 在 M 的修改順序中出現在 A 之後的一些不相關的對 M 的修改。

對於 M 上的原子修改 A 和 B,如果滿足以下任一條件,則 B 在 M 的修改順序中出現在 A 之後:

  • 存在一個 memory_order_seq_cst std::atomic_thread_fence X,使得 A *順序前* X,並且 X 在單一總序中出現在 B 之前,
  • 或者,存在一個 memory_order_seq_cst std::atomic_thread_fence Y,使得 Y *順序前* B,並且 A 在單一總序中出現在 Y 之前,
  • 或者,存在 memory_order_seq_cst std::atomic_thread_fence X 和 Y,使得 A *順序前* X,Y *順序前* B,並且 X 在單一總序中出現在 Y 之前。

請注意,這意味著

1) 一旦未標記 memory_order_seq_cst 的原子操作進入圖片,程式的順序一致性就喪失了,
2) 在許多情況下,memory_order_seq_cst 原子操作可以與同一執行緒執行的其他原子操作重新排序(*順序前*不是跨執行緒關係,不像*先行發生*)。
(C++20 前)
形式上,

原子操作 A 在某個原子物件 M 上*一致性有序前*另一個原子操作 B 在 M 上,如果以下任一條件為真:

1) A 是一個修改,並且 B 讀取 A 儲存的值,
2) A 在 M 的*修改順序*中先於 B,
3) A 讀取原子修改 X 儲存的值,X 在*修改順序*中先於 B,並且 A 和 B 不是同一個原子讀-修改-寫操作,
4) A *一致性有序前* X,且 X *一致性有序前* B。

所有 memory_order_seq_cst 操作(包括屏障)上存在一個單一總序 S,它滿足以下約束:

1) 如果 A 和 B 是 memory_order_seq_cst 操作,且 A *強先行發生* B,則 A 在 S 中先於 B,
2) 對於物件 M 上的每對原子操作 A 和 B,其中 A *一致性有序前* B
a) 如果 A 和 B 都是 memory_order_seq_cst 操作,則 A 在 S 中先於 B,
b) 如果 A 是 memory_order_seq_cst 操作,且 B *先行發生* memory_order_seq_cst 屏障 Y,則 A 在 S 中先於 Y,
c) 如果 memory_order_seq_cst 屏障 X *先行發生* A,且 B 是 memory_order_seq_cst 操作,則 X 在 S 中先於 B,
d) 如果 memory_order_seq_cst 屏障 X *先行發生* A,且 B *先行發生* memory_order_seq_cst 屏障 Y,則 X 在 S 中先於 Y。

正式定義確保

1) 單一總序與任何原子物件的*修改順序*一致,
2) memory_order_seq_cst 載入要麼從最後一次 memory_order_seq_cst 修改獲取其值,要麼從不*先行發生*先行 memory_order_seq_cst 修改的非 memory_order_seq_cst 修改獲取其值。

單一總序可能與*先行發生*不一致。這允許在某些 CPU 上更有效地實現 memory_order_acquirememory_order_release。當 memory_order_acquirememory_order_releasememory_order_seq_cst 混合使用時,它可能會產生令人驚訝的結果。

例如,如果 `x` 和 `y` 最初為零,

// Thread 1:
x.store(1, std::memory_order_seq_cst); // A
y.store(1, std::memory_order_release); // B
// Thread 2:
r1 = y.fetch_add(1, std::memory_order_seq_cst); // C
r2 = y.load(std::memory_order_relaxed); // D
// Thread 3:
y.store(3, std::memory_order_seq_cst); // E
r3 = x.load(std::memory_order_seq_cst); // F

允許產生 r1 == 1 && r2 == 3 && r3 == 0,其中 A *先行發生* C,但 C 在 memory_order_seq_cst 的單一總序 C-E-F-A 中先於 A(參見 Lahav 等人)。

注意:

1) 一旦未標記 memory_order_seq_cst 的原子操作進入,程式的順序一致性保證就丟失了,
2) 在許多情況下,memory_order_seq_cst 原子操作可以與同一執行緒執行的其他原子操作重新排序。
(C++20 起)

對於多生產者-多消費者場景,其中所有消費者必須以相同的順序觀察所有生產者的操作,順序排序可能是必要的。

總順序一致性在所有多核系統上都需要一個完整的記憶體屏障 CPU 指令。這可能成為效能瓶頸,因為它強制受影響的記憶體訪問傳播到每個核心。

此示例演示了順序排序必要的場景。任何其他排序都可能觸發斷言,因為執行緒 cd 有可能以相反的順序觀察原子 xy 的更改。

#include <atomic>
#include <cassert>
#include <thread>
 
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
 
void write_x()
{
    x.store(true, std::memory_order_seq_cst);
}
 
void write_y()
{
    y.store(true, std::memory_order_seq_cst);
}
 
void read_x_then_y()
{
    while (!x.load(std::memory_order_seq_cst))
        ;
    if (y.load(std::memory_order_seq_cst))
        ++z;
}
 
void read_y_then_x()
{
    while (!y.load(std::memory_order_seq_cst))
        ;
    if (x.load(std::memory_order_seq_cst))
        ++z;
}
 
int main()
{
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join(); b.join(); c.join(); d.join();
    assert(z.load() != 0); // will never happen
}

[編輯] volatile 的關係

在一個執行執行緒內,透過volatile glvalues的訪問(讀和寫)不能被重新排序到同一執行緒內*順序前*或*順序後*的可觀察副作用(包括其他 volatile 訪問)之後,但這種順序不保證被另一個執行緒觀察到,因為 volatile 訪問不建立執行緒間同步。

此外,volatile 訪問不是原子的(併發讀寫是資料競爭),並且不進行記憶體排序(非 volatile 記憶體訪問可以在 volatile 訪問周圍自由重新排序)。

一個值得注意的例外是 Visual Studio,在預設設定下,每個 volatile 寫入都具有釋放語義,每個 volatile 讀取都具有獲取語義(Microsoft Docs),因此 volatile 可以用於執行緒間同步。標準 volatile 語義不適用於多執行緒程式設計,儘管它們足以用於例如與在同一執行緒中執行的 std::signal 處理程式通訊(當應用於 sig_atomic_t 變數時)。

[編輯] 另請參閱

C 文件,關於 記憶體順序

[編輯] 外部連結

1.  MOESI 協議
2.  x86-TSO: 適用於 x86 多處理器的嚴格且可用的程式設計師模型 P. Sewell 等,2010
3.  ARM 和 POWER 寬鬆記憶體模型入門教程 P. Sewell 等,2012
4.  MESIF:點對點互連的雙跳快取一致性協議 J.R. Goodman, H.H.J. Hum, 2009
5.  記憶體模型 Russ Cox, 2021