名前空間
変種
操作

std::memory_order

From 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)
future
(C++11)
(C++11)
(C++11)
(C++11)
安全なメモリ解放 (Safe Reclamation)
(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;
<メモリー・オーダー・アキwire_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より順序付け前 (sequenced-before) になることがあります。

依存関係の伝搬 (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まで)

[編集] 変更順序 (Modification order)

特定の任意のアトミック変数に対するすべての変更は、そのアトミック変数に固有の全順序で発生します。

すべてのアトミック操作について、以下の4つの要件が保証されます。

1) 書き込み-書き込み一貫性:あるアトミックMを変更する(書き込み)評価Aが、Mを変更する評価Bより先行発生する場合、AはMの変更順序においてBより前に現れます。
2) 読み取り-読み取り一貫性:あるアトミックMの値計算(読み取り)Aが、Mに対する値計算Bより先行発生し、かつAの値がMへの書き込みXに由来する場合、Bの値はXによって格納された値か、またはMの変更順序においてXより後に現れるMへの副作用Yによって格納された値のいずれかです。
3) 読み取り-書き込み一貫性:あるアトミックMの値計算(読み取り)Aが、Mに対する操作B(書き込み)より先行発生する場合、Aの値はMの変更順序においてBより前に現れる副作用(書き込み)Xに由来します。
4) 書き込み-読み取り一貫性:アトミックオブジェクトMへの副作用(書き込み)Xが、Mの値計算(読み取り)Bより先行発生する場合、評価Bはその値をXから、またはMの変更順序でXに続く副作用Yから取得しなければなりません。

[編集] リリースシーケンス (Release sequence)

アトミックオブジェクト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 et al を参照)。

一方の評価がメモリ位置を変更し、もう一方が同じメモリ位置を読み取るか変更し、かつ少なくとも一方の評価がアトミック操作でない場合、これら2つの評価間に先行発生関係が存在しない限り、プログラムの動作は未定義です(プログラムにはデータ競合があります)。

単純先行発生 (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がYより単純(C++26まで)先行発生し、YがBより順序付け前である場合。
4) AがXより強く先行発生し、かつXがBより強く先行発生する場合。

注:非公式には、AがBより強く先行発生する場合、AはすべてのコンテキストでBより前に評価されたように見えます。

注:強く先行発生はコンシューム操作を除外します。

(C++26まで)
(C++20以降)

[編集] 可視な副作用 (Visible side-effects)

スカラMに対する副作用A(書き込み)は、Mに対する値計算B(読み取り)に関して可視であるとは、以下の両方が真である場合です。

1) AがBより先行発生する。
2) AがXより先行発生し、かつXがBより先行発生するようなMへの他の副作用Xが存在しない。

副作用Aが値計算Bに対して可視である場合、変更順序におけるMへの副作用の最長の連続した部分集合で、Bがそれより先行発生しないものは、副作用の可視シーケンスとして知られています(Bによって決定されるMの値は、これらの副作用のいずれかによって格納された値になります)。

注:スレッド間の同期は、データ競合を防ぎ(先行発生関係を確立することによって)、どのような条件下でどの副作用が可視になるかを定義することに帰着します。

[編集] コンシューム操作 (Consume operation)

memory_order_consume またはそれより強いメモリ順序を持つアトミックロードは、コンシューム操作です。 std::atomic_thread_fence はコンシューム操作よりも強い同期要件を課すことに注意してください。

[編集] アキュワイア操作 (Acquire operation)

memory_order_acquire またはそれより強いメモリ順序を持つアトミックロードは、アキュワイア操作です。 Mutex に対する lock() 操作もアキュワイア操作です。 std::atomic_thread_fence はアキュワイア操作よりも強い同期要件を課すことに注意してください。

[編集] リリース操作 (Release operation)

memory_order_release またはそれより強いメモリ順序を持つアトミックストアは、リリース操作です。 Mutex に対する unlock() 操作もリリース操作です。 std::atomic_thread_fence はリリース操作よりも強い同期要件を課すことに注意してください。

[編集] 説明

[編集] 緩和された順序付け (Relaxed ordering)

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

スレッド1内ではAがBより順序付け前であり、スレッド2内ではCがDより順序付け前であるにもかかわらず、Dがyの変更順序においてAより前に現れ、Bがxの変更順序においてCより前に現れることを妨げるものがないため、r1 == r2 == 42 という結果を生むことが許されます。yに対するDの副作用がスレッド1のロードAに可視となり、xに対するBの副作用がスレッド2のロードCに可視となる可能性があります。特に、コンパイラのリオーダリングや実行時に、スレッド2内でDがCより先に完了した場合にこれが起こり得ます。

緩和されたメモリモデルであっても、無から生じた値(out-of-thin-air value)が自身の計算に循環的に依存することは許されません。例えば、初期値がゼロの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);

yへの42のストアは、xへのストアが42をストアした場合にのみ可能であり、それはyへのストアが42をストアすることに循環的に依存しているため、r1 == r2 == 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

[編集] リリース-アキュワイア順序付け (Release-Acquire ordering)

スレッド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();
}

以下の例は、リリースシーケンスを用いて、3つのスレッドにまたがる推移的なリリース-アキュワイア順序付けを示しています。

#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();
}

[編集] リリース-コンシューム順序付け (Release-Consume ordering)

スレッド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();
}


[編集] シーケンシャル整合性のある順序付け (Sequentially-consistent ordering)

memory_order_seq_cstでタグ付けされたアトミック操作は、リリース/アキュワイア順序付けと同じようにメモリを順序付けるだけでなく(あるスレッドでのストアより先行発生したすべてが、ロードを行ったスレッドで可視な副作用になる)、そのようにタグ付けされたすべてのアトミック操作の単一の全変更順序を確立します。

正式には、

アトミック変数Mからロードする各memory_order_seq_cst操作Bは、以下のいずれかを観測します。

  • 単一の全順序においてBより前に現れる、Mを変更した最後の操作Aの結果。
  • あるいは、そのようなAがあった場合、Bはmemory_order_seq_cstではなく、Aより先行発生しないMに対する何らかの変更の結果を観測するかもしれない。
  • あるいは、そのようなAがなかった場合、Bはmemory_order_seq_cstではない、Mに対する無関係な変更の結果を観測するかもしれない。

Bより順序付け前memory_order_seq_cst std::atomic_thread_fence 操作Xがあった場合、Bは以下のいずれかを観測します。

  • 単一の全順序においてXより前に現れるMの最後のmemory_order_seq_cst変更。
  • Mの変更順序において後に現れる、Mに対する無関係な何らかの変更。

Mに対するアトミック操作のペアAとBがあり、Aが書き込み、BがMの値を読み取る場合、2つの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の後に発生するのは、

  • AがXより順序付け前であり、Xが単一全順序においてBより前に現れるようなmemory_order_seq_cst std::atomic_thread_fence Xが存在する場合。
  • または、YがBより順序付け前であり、Aが単一全順序においてYより前に現れるようなmemory_order_seq_cst std::atomic_thread_fence Yが存在する場合。
  • あるいは、AがXより順序付け前であり、YがBより順序付け前であり、Xが単一全順序においてYより前に現れるようなmemory_order_seq_cst std::atomic_thread_fence XとYが存在する場合。

これは次のことを意味することに注意してください。

1) memory_order_seq_cst タグが付いていないアトミック操作が関わるとすぐに、シーケンシャル整合性は失われます。
2) シーケンシャル整合性のあるフェンスは、フェンス自体の全順序を確立するだけであり、一般的な場合のアトミック操作に対してはそうではありません(順序付け前先行発生とは異なり、スレッド間の関係ではありません)。
(C++20まで)
正式には、

あるアトミックオブジェクトMに対するアトミック操作Aが、Mに対する別のアトミック操作Bよりコヒーレンス順序付け前であるとは、以下のいずれかが真である場合です。

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と混合すると、驚くべき結果を生む可能性があります。

例えば、初期値がゼロのxyについて、

// 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

AがCより先行発生するにもかかわらず、memory_order_seq_cstの単一全順序 C-E-F-A においてCがAより前に来るため、r1 == 1 && r2 == 3 && r3 == 0 という結果を生むことが許されます(Lahav et al参照)。

注意点:

1) memory_order_seq_cstのタグが付いていないアトミック操作が関わるようになると、プログラムのシーケンシャル整合性の保証は失われます。
2) 多くの場合、memory_order_seq_cstアトミック操作は、同じスレッドによって実行される他のアトミック操作に対してリオーダリング可能です。
(C++20以降)

シーケンシャル順序付けは、すべてのコンシューマがすべてのプロデューサーの行動を同じ順序で観測する必要がある、複数プロデューサー・複数コンシューマの状況で必要になる場合があります。

完全なシーケンシャル順序付けは、すべてのマルチコアシステムで完全なメモリフェンスCPU命令を必要とします。これは、影響を受けるメモリアクセスをすべてのコアに伝播させる必要があるため、パフォーマンスのボトルネックになる可能性があります。

この例は、シーケンシャル順序付けが必要な状況を示しています。他のどの順序付けでも、スレッド `c` と `d` がアトミック変数 `x` と `y` への変更を逆の順序で観測する可能性があるため、アサートがトリガーされる可能性があります。

#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なglvalueを介したアクセス(読み取りと書き込み)は、同じスレッド内で順序付け前または順序付け後である観測可能な副作用(他のvolatileアクセスを含む)を越えてリオーダリングすることはできません。しかし、volatileアクセスはスレッド間の同期を確立しないため、この順序が他のスレッドによって観測されることは保証されません。

さらに、volatileアクセスはアトミックではなく(並行した読み書きはデータ競合です)、メモリを順序付けません(非volatileなメモリアクセスはvolatileアクセスの前後で自由にリオーダリングされる可能性があります)。

一つの注目すべき例外はVisual Studioです。デフォルト設定では、すべてのvolatile書き込みはリリースセマンティクスを持ち、すべてのvolatile読み取りはアキュワイアセマンティクスを持ちます(Microsoft Docs)。したがって、volatileはスレッド間同期に使用できます。標準のvolatileセマンティクスはマルチスレッドプログラミングには適用できませんが、sig_atomic_t変数に適用した場合、同じスレッドで実行されるstd::signalハンドラとの通信などには十分です。

[編集] 関連項目

メモリ順序についてのC言語のドキュメンテーション

[編集] 外部リンク

1.  MOESIプロトコル
2.  x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors P. Sewell et. al., 2010
3.  A Tutorial Introduction to the ARM and POWER Relaxed Memory Models P. Sewell et al, 2012
4.  MESIF: A Two-Hop Cache Coherency Protocol for Point-to-Point Interconnects J.R. Goodman, H.H.J. Hum, 2009
5.  Memory Models Russ Cox, 2021
English 日本語 中文(简体) 中文(繁體)