1. ホーム
  2. c++

それぞれのmemory_orderは何を意味しているのでしょうか?

2023-12-16 08:30:22

質問

一章を読みましたが、あまり好きになれませんでした。各記憶順序の違いは何なのか、まだ不明です。これは私の現在の推測ですが、もっとシンプルな http://en.cppreference.com/w/cpp/atomic/memory_order

以下は間違いですので、そこから学ぼうとしないでください。

  • memory_order_relaxed: 同期しないが、別のモードから別のアトミックvarでオーダーした場合は無視しない。
  • memory_order_consume。このアトム変数の読み込みを同期させるが、これより前に書かれたリラックスした変数は同期させない。しかし、もしスレッドが Y を変更するときに変数 X を使用するならば (そしてそれを解放する)。Yを消費する他のスレッドは、Xが同様に解放されるのを見るのだろうか?私はこれがこのスレッドがxの変更(そして明らかにy)を押し出すことを意味するかどうかわかりません。
  • memory_order_acquire: このアトミック変数の読み込みを同期し、これより前に書かれたリラックスした変数も同様に同期されることを確認します。(これは、すべてのスレッド上のすべての原子変数が同期されることを意味しますか?)
  • memory_order_release。アトミックストアを他のスレッドにプッシュします(ただし、consume/acquire で var を読み込んだ場合のみ)。
  • memory_order_acq_rel: read/write 操作のためのものです。古い値を変更しないように獲得し、変更を解放します。
  • memory_order_seq_cst。更新が他のスレッドで見られるように強制する以外は、acquire release と同じものです (もし a を他のスレッドでリラックスして保存します。私は b をseq_cstで格納します。3番目のスレッドが読み a をrelaxで読んでいる3番目のスレッドでは b およびその他のアトミック変数?)

私は理解したと思いますが、私が間違っているならば、私を修正します。読みやすい英語で説明しているものは見つかりませんでした。

どのように解決するのですか?

GCC Wiki では があり、とても丁寧でわかりやすい説明になっています。 をコード例とともに提供しています。

(抜粋編集、強調)

重要なこと

答えに自分自身の言葉を加える過程でGCC Wikiからコピーした以下の引用を読み直したとき、その引用が実際には間違っていることに気づきました。彼らは を取得します。 消費する は全く間違った方法です。A リリース-コンシューム 操作は依存するデータの順序を保証するだけであるのに対し リリース・アキュア 操作では、データがアトム値に依存しているかどうかに関係なく、その保証を提供します。

最初のモデルは"sequentially consistent"です。これは何も指定されていないときに使用されるデフォルトのモードで、最も制約が多い。また、このモードは memory_order_seq_cst . これは 逐次プログラマが本質的に慣れ親しんでいる、負荷を移動させるための制限と同じものを提供しますが、スレッド間で適用されることを除けば .

[...]

実用的な観点からは、これはすべてのアトミック操作が最適化バリアとして機能することに等しい。アトミック操作の間で物事を並べ替えるのは良いのですが、操作全体ではダメなのです。他のスレッドへの可視性がないため、スレッドローカルのものも影響を受けません。[...] また、このモードは すべての スレッド間の一貫性も保たれます。

この 反対アプローチ memory_order_relaxed . このモデルでは、happens-beforeの制約を取り除くことで、同期を大幅に削減することができます。この種のアトミックな操作は、デッドストアの除去やコモニングなど、様々な最適化も可能です。[中略)happened-beforeのエッジがなければ、どのスレッドも他のスレッドからの特定の順序をあてにすることはできません。

リラックスモードは プログラマが単に変数がアトミックであることを望むときに最も一般的に使用されます。 他の共有メモリデータのためにスレッドを同期させるために使用するのではなく、プログラマが単にアトミックであることを望む場合、最も一般的に使用されます。

3番目のモード ( memory_order_acquire / memory_order_release ) は ハイブリッド の間にあります。アクワイア/リリース・モードはシーケンシャル・コンシステント・モードに似ていますが、それ以外は は従属変数にhappens-before関係を適用するだけです。 . これにより、独立した読み出しと独立した書き込みの間に必要な同期を緩和することができます。

memory_order_consume は、リリース/アクワイアメモリモデルにおけるさらなる微妙な改良で、要件をわずかに緩和するために 非依存型共有変数の順序付けの前に起こることを削除します。 .

[...]

本当の違いは、ハードウェアが同期をとるためにどれだけの状態をフラッシュしなければならないかに集約されます。消費操作では したがって、自分が何をしているかを知っている人は、パフォーマンスが重要なアプリケーションにこれを使用することができます。

以下は、より平凡な説明のための私自身の試みです。

これを見るための別のアプローチは、アトミックと通常の両方の読み取りと書き込みの順序を変更するという観点から問題を見ることです。

すべて の組み合わせは、それ自身の中でアトミックであることが保証されています。 2 の組み合わせは全体としてアトミックではありません!)、実行ストリームのタイムライン上に表示される総順序で表示されることが保証されています。つまり、どのような状況であっても、原子操作は順序を変更できませんが、他のメモリ操作は順序を変更できる可能性が非常に高いということです。コンパイラ(およびCPU)は、最適化としてこのような再順序付けを日常的に行っています。

また、コンパイラーは、任意の時点で実行されるアトミック演算が、前に実行された他のすべてのアトミック演算の結果を参照することを保証するために必要な命令を使用しなければならないことを意味します(別のプロセッサ コアの可能性もあります)。

では リラックスした はあくまで最低限のものです。追加で何かをするわけでもなく、他の保証を提供するわけでもない。これは最も安価な操作です。強行順序プロセッサ アーキテクチャ (例: x86/amd64) の非 read-modify-write 操作では、これはごく普通の普通の移動に帰着します。

順次一貫した 操作は正反対で、アトム操作だけでなく、その前後に起こる他のメモリ操作に対しても厳密な順序付けを強制します。どちらもアトミック操作によって課せられた障壁を越えることはできません。現実的には、最適化の機会を失うことになり、場合によってはフェンス命令を挿入しなければならないかもしれない。これは最も高価なモデルです。

A リリース 操作によって、通常のロードとストアの順序が変更されることはありません。 アトミック操作の後に 取得 操作では、通常のロードとストアの順序を変更することはできません。 前に アトミック操作の前に並べ替えられることを防ぎます。他のすべてはまだ移動することができます。

ストアがアトミック操作の後に移動されるのを防ぎ、ロードがそれぞれのアトミック操作の前に移動されるのを防ぐという組み合わせにより、取得するスレッドが見るものはすべて一貫性があり、最適化の機会がほんの少し失われるだけであることを確認します。

これは、(ライターによって)解放され、(リーダーによって)取得される、存在しないロックのようなものだと考えることができます。ただし...ロックは存在しません。

実際には、リリース/アクワイアは通常、コンパイラが特に高価な特殊命令を使用する必要がないことを意味しますが、それは はできません。

は自由にロードとストアを並べ替えることができないため、いくつかの (小さな) 最適化の機会を逃してしまう可能性があります。

最後に 消費する と同じ操作で 取得 と同じ操作ですが、順序の保証が従属データにのみ適用されるという例外があります。依存するデータとは、例えば、原子的に変更されたポインタによって指されるデータのことです。

しかし、これは、より複雑でエラーが起こりやすいコードと、依存関係の連鎖を正しく得るための自明ではないタスクを犠牲にして行われます。

現在のところ を使用することは推奨されません。 の使用は推奨されません。