名前空間
変種
操作

memory_order

提供: cppreference.com
< c‎ | atomic
ヘッダ <stdatomic.h> で定義
enum memory_order {

    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst

};
(C11およびそれ以降)

memory_order は、通常の非アトミックなメモリアクセスがアトミック操作の周りでどのように順序付けられるかを指定します。 マルチコアシステムにおいて、いかなる制約もなければ、複数のスレッドがいくつかの変数を同時に読み書きしたとき、あるスレッドが書き込んだ順序とは異なる順序で別のスレッドからその値の変更が観察される可能性があります。 実際、変更が見える順序は複数の読み込みスレッド間で異なる可能性さえあります。 シングルコアシステムにおいてさえも、メモリモデルによって許されているコンパイラの変換のために、同様の効果が発生する可能性があります。

言語およびライブラリのすべてのアトミック操作が提供するデフォルトの動作は、逐次一貫性順序付けです (後述の説明を参照してください)。 このデフォルトは性能的に不利ですが、ライブラリのアトミック操作では、アトミック性だけではなく、その操作に対してコンパイラおよびプロセッサが強制しなければならない正確な制約を指定する、追加の memory_order 引数を渡すことができます。

目次

[編集] 定数

ヘッダ <stdatomic.h> で定義
説明
memory_order_relaxed 緩和された操作。 他の読み書きに対して課される順序制約や同期はなく、この操作のアトミック性のみが保証されます (後述の緩和された順序付けを参照してください)。
memory_order_consume このメモリ順序を持つロード操作は、影響を受けるメモリ位置に対して消費操作を行います。 現在ロード中の値に依存する現在のスレッド内の読み書きは、このロードの前に順序変更されることはできません。 同じアトミック変数を解放する他のスレッドのデータ依存変数への書き込みは、現在のスレッド内で可視になります。 ほとんどのプラットフォームでは、これはコンパイラの最適化にのみ影響を与えます (後述の解放-消費順序付けを参照してください)。
memory_order_acquire このメモリ順序を持つロード操作は、影響を受けるメモリ位置に対して取得操作を行います。 現在のスレッド内の読み書きは、このロードの前に順序変更されることはできません。 同じアトミック変数を解放する他のスレッドのすべての書き込みは、現在のスレッド内で可視になります (後述の消費-取得順序付けを参照してください)。
memory_order_release このメモリ順序を持つストア操作は、解放操作を行います。 現在のスレッド内の読み書きは、このストアの後に順序変更されることはできません。 現在のスレッド内のすべての書き込みは、同じアトミック変数を取得する別のスレッド内で可視になり (後述の解放-取得順序付けを参照してください)、アトミック変数に依存性を伝播する書き込みは、同じアトミック変数を消費する他のスレッド内で可視になります (後述の解放-消費順序付けを参照してください)。
memory_order_acq_rel このメモリ順序を持つ読み込み-変更-書き込み操作は、取得操作解放操作の両方を行います。 現在のスレッド内のメモリの読み書きは、このストアの前や後に順序変更されることはできません。 同じアトミック変数を解放する他のスレッド内のすべての書き込みは、この変更の前に可視になり、この変更は、同じアトミック変数を取得する他のスレッド内で可視になります。
memory_order_seq_cst このメモリ順序を持つロード操作は解放操作を行い、ストア操作は取得操作を行い、読み込み-変更-書き込み操作は取得操作解放操作の両方を行い、さらにすべてのスレッドがすべての変更を同じ順序で観察する単一の全順序が存在します (後述の逐次一貫性順序付けを参照してください)。

[編集] 緩和された順序付け

memory_order_relaxed が指定されたアトミック操作は、同期操作ではありません。 これは並行的なメモリアクセス間に順序制約を課しません。 アトミック性と変更順序の一貫性のみが保証されます。

例えば、初期値がゼロの xy を用いて、以下のコードを考えてみます。

// スレッド 1:
r1 = atomic_load_explicit(y, memory_order_relaxed); // A
atomic_store_explicit(x, r1, memory_order_relaxed); // B
// スレッド 2:
r2 = atomic_load_explicit(x, memory_order_relaxed); // C
atomic_store_explicit(y, 42, memory_order_relaxed); // D

このコードは、 r1 == r2 == 42 を生成する可能性があります。 スレッド 1 では A が B に対して先行配列され、スレッド 2 では C が D に対して先行配列されますが、 y の変更順序において D が A より前に現れることを阻むものはなく、 x の変更順序において B が C より前に現れることを阻むものもないためです。 y に対する D の副作用がスレッド 1 のロード A に可視になるのと同時に、 x に対する B の副作用がスレッド 2 のロード C に可視になる可能性があります。 特に、コンパイラや実行時の順序変更のためにスレッド 2 において C より前に D が完了すれば、これが発生する可能性があります。


緩和されたメモリ順序の一般的な用途は、参照カウンタのような、カウンタのインクリメントです。 これはアトミック性のみが要求され、順序付けや同期は不要なためです (ただし、 shared_ptr カウンタのデクリメントでは、デストラクタとの間で取得-解放同期が要求されることに注意してください)。

[編集] 解放-消費順序付け

スレッド A のアトミックストアに memory_order_release が指定されており、スレッド B の同じ変数からのアトミックロードに memory_order_consume が指定されている場合、スレッド A の視点でそのアトミックストアに対して先行依存順序付けされるすべてのメモリ書き込み (非アトミックなものおよび緩和されたアトミックなもの) は、スレッド B でそのロード操作が依存性を伝播する操作内で可視な副作用になります。 つまり、いったんそのアトミックロードが完了すれば、そのロードから取得した値を使用するスレッド B の操作や関数は、スレッド A がメモリに書き込んだものを見ることが保証されます。

同期は、同じアトミック変数を解放および消費するスレッド間でのみ確立されます。 他のスレッドは、同期しているスレッドのいずれか、あるいはいずれとも異なる順序でメモリアクセスを見る可能性があります。

DEC Alpha を除くすべての主流な CPU では、依存順序付けは自動的に適用されます。 この同期モードに対して追加の CPU 命令は発行されません。 特定のコンパイラの最適化に影響するだけです (例えば、コンパイラは依存連鎖に影響するオブジェクトを投機的にロードすることはできません)。

この順序付けの一般的なユースケースは、滅多に書き込まれない並行データ構造 (ルーティングテーブル、コンフィグレーション、セキュリティポリシー、ファイアウォールのルールなど) への読み込みアクセスや、ポインタを介した公開を行う出版-購読のパターン、つまり、生産者がポインタを公開し、消費者がそのポインタを通して情報にアクセスする場合です。 消費者に可視なメモリに生産者が書き込む以外のこと (弱い順序付けのアーキテクチャでは高価な操作となる場合があります) は、する必要がありません。 そのようなシナリオの例は rcu_dereference です。


現在 (2015年2月)、依存連鎖を追跡する既知の製品コンパイラはありません。 消費操作は取得操作に引き上げられます。

[編集] 解放シーケンス

何らかのアトミックオブジェクトが解放ストアされ、いくつかの他のスレッドがそのアトミックオブジェクトに対して読み込み-変更-書き込み操作を行う場合、「解放シーケンス」が形成されます。 その同じアトミックオブジェクトに読み込み-変更-書き込み操作を行ったすべてのスレッドは、 memory_order_release セマンティクスを持たなくても、その最初のスレッドおよびお互いに対して同期します。 これは個々の消費者スレッド間に不要な同期を課さずに単一の生産者と多数の消費者の状況を可能性にします。

[編集] 解放-取得順序付け

スレッド A のアトミックストアに memory_order_release が指定されており、スレッド B の同じ変数からのアトミックロードに memory_order_acquire が指定されている場合、スレッド A の視点でそのアトミックストアに対して先行発生するすべてのメモリ書き込み (非アトミックなものおよび緩和されたアトミックなもの) は、スレッド B で可視な副作用になります。 つまり、いったんそのアトミックロードが完了すれば、スレッド B は、スレッド A がメモリに書き込んだすべてのものを見ることが保証されます。

同期は、同じアトミック変数を解放および取得するスレッド間でのみ確立されます。 他のスレッドは、同期しているスレッドのいずれか、あるいはいずれとも異なる順序でメモリアクセスを見る可能性があります。

強い順序付けを持つシステム (x86、SPARC TSO、IBM のメインフレーム) では、解放-取得順序付けは、多くの操作に対して自動的に適用されます。 この同期モードに対して追加の CPU 命令は発行されません。 特定のコンパイラの最適化に影響するだけです (例えば、コンパイラは非アトミックストアを解放アトミックストアの後に移動したり、非アトミックロードを取得アトミックロードより前に行ったりすることはできません)。 弱い順序付けを持つシステム (ARM、Itanium、PowerPC) では、 CPU の特別なロード命令やメモリフェンス命令が使用されます。

相互排他ロック (ミューテックスアトミックなスピンロックなど) は、解放-取得同期の例です。 ロックがスレッド A によって解放され、スレッド B によって取得されるとき、スレッド A のコンテキストにおいてクリティカルセクション内で (ロック解放前に) 行われたすべてのことは、同じクリティカルセクションを実行している (ロック取得後の) スレッド B に対して、可視にならなければなりません。

[編集] 逐次一貫性順序付け

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_cstatomic_thread_fence 操作 X が存在した場合、 B は以下のいずれかを観察します。

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

M の値を書き込むアトミック操作 A および M の値を読み込むアトミック操作 B について、2つの memory_order_seq_cstatomic_thread_fence 操作 X および Y が存在し、 A が X に対して先行配列され、 Y が B に対して先行配列され、単一の全順序において X が Y より前に現れる場合、 B は以下のいずれかを観察します。

  • A の効果。
  • M の変更順序において A の後に現れる何らかの無関係な M の変更。

M のアトミックな変更操作 A および B について、以下の場合、 M の変更順序において、 B が A より後に発生します。

  • A が X に対して先行配列され、単一の全順序において X が B より前に現れるような、 memory_order_seq_cstatomic_thread_fence 操作 X が存在する。
  • または、 Y が B に対して先行配列され、単一の全順序において A が Y より前に現れるような、 memory_order_seq_cstatomic_thread_fence 操作 Y が存在する。
  • または、 A が X に対して先行配列され、 Y が B に対して先行配列され、単一の全順序において X が Y より前に現れるような、 memory_order_seq_cstatomic_thread_fence 操作 X および Y が存在する。

これは以下の事柄を意味することに注意してください。

1) memory_order_seq_cst でないアトミック操作が登場した途端、逐次一貫性は失われます。
2) 逐次一貫性のフェンスは、一般的なケースのアトミック操作に対してではなく、フェンス自身に対して全順序を確立するだけです (先行発生と異なり、先行配列はスレッド間の関係ではありません)。

複数の生産者と複数の消費者がいる状況で、すべての生産者が起こしたアクションを同じ順序ですべての消費者が観察しなければならない場合、逐次一貫性が必要になるかもしれません。

すべてのマルチコアシステムにおいて、逐次的な全順序は CPU のフルメモリフェンス命令を要求します。 これは影響のあるメモリアクセスをすべてのコアに伝搬することを強制するため、性能のボトルネックになる場合があります。

[編集] volatile との関係

ある実行のスレッド内で、 volatile lvalue を通したアクセス (読み込みおよび書き込み) は、同じスレッド内のシーケンスポイントで区切られた観察可能な副作用 (他の volatile アクセスも含む) を超えて順序変更されることはできませんが、 volatile アクセスはスレッド間の同期を確立しないため、この順序が他のスレッドから観察できることは保証されません。

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

ひとつの重要な例外は Visual Studio です。 Visual Studio では、デフォルトの設定では、すべての volatile 書き込みは解放操作であり、すべての volatile 読み込みは取得操作であり (MSDN)、そのため volatile をスレッド間の同期に使用することができます。 標準の volatile の意味論は、例えば sig_atomic_t 変数に適用すれば、同じスレッドで実行する signal ハンドラと通信するためには十分ですが、マルチスレッドプログラミングに適用することはできません。

[編集]

[編集] 参考文献

  • C11 standard (ISO/IEC 9899:2011):
  • 7.17.1/4 memory_order (p: 273)
  • 7.17.3 Order and consistency (p: 275-277)

[編集] 関連項目

memory orderC++リファレンス

[編集] 外部リンク