6

C++11以降で知られているように、6つのメモリ順序があり、ドキュメントには次のように書かれていstd::memory_order_acquireます:

memory_order_acquire

このメモリ順序での読み込み操作は、影響を受けるメモリ位置で取得操作を実行します。現在のスレッドのメモリ アクセスは、この読み込みの前に並べ替えることができません。これにより、同じアトミック変数を解放する他のスレッドでのすべての書き込みが、現在のスレッドで確実に表示されます。

1. 非アトミックロードは、アトミック取得ロードの後に​​並べ替えることができます。

つまり、acquire-atomic-load の後に non-atomic-load を並べ替えることができないという保証はありません。

static std::atomic<int> X;
static int L;
...

void thread_func() 
{
    int local1 = L;  // load(L)-load(X) - can be reordered with X ?

    int x_local = X.load(std::memory_order_acquire);  // load(X)

    int local2 = L;  // load(X)-load(L) - can't be reordered with X
}

の後にロードint local1 = L;を並べ替えることはできますX.load(std::memory_order_acquire);か?

2. 非アトミック ロードは、アトミック アクワイア ロードの後に​​並べ替えることができないと考えることができます。

一部の記事には、取得と解放のセマンティクスの本質を示す図が含まれていました。これは簡単に理解できますが、混乱を招く可能性があります。

ここに画像の説明を入力

ここに画像の説明を入力

たとえば、std::memory_order_acquire一連の Load-Load 操作を並べ替えることができないと考えるかもしれません。非アトミック ロードであっても、atomic-acquire-load の後で並べ替えることはできません。

3. 非アトミックロードは、アトミック取得ロードの後に​​並べ替えることができます。

明確化されていることは良いことです: セマンティクスを取得することで、プログラム順序でそれに続く読み取りまたは書き込み操作による読み取り/取得のメモリの並べ替えが防止されます。http://preshing.com/20120913/acquire-and-release-semantics/

しかし、次のことも知られています: 厳密に順序付けされたシステム ( x86、SPARC TSO、IBM メインフレーム) では、大部分の操作で解放と取得の順序付けが自動的に行われます。

また、34 ページの Herb Sutter には次のように示されています

ここに画像の説明を入力

4. つまり、atomic-acquire-load の後に non-atomic-load を並べ替えることはできないと考えることができます。

つまり、x86 の場合:

  • リリースと取得の順序付けは、ほとんどの操作で自動的に行われます
  • 読み取りは、どの読み取りでも並べ替えられません。(任意 - つまり、古いかどうかに関係なく)

では、C++11 でアトミック取得ロードの後に​​非アトミックロードを並べ替えることができますか?

4

4 に答える 4

4

あなたが引用した参照は非常に明確です。このロードの前に読み取りを移動することはできません。あなたの例では:

static std::atomic<int> X;
static int L;


void thread_func() 
{
    int local1 = L;  // (1)
    int x_local = X.load(std::memory_order_acquire);  // (2)
    int local2 = L;  // (3)
}

memory_order_acquire(3) が (2) の前に発生することはあり得ないことを意味します ((2) のロードは (3) のロードの前にシーケンスされます)。(1) と (2) の関係については何も述べていません。

于 2016-07-30T18:40:40.243 に答える
0

このメモリ順序での読み込み操作は、影響を受けるメモリ位置で取得操作を実行します。現在のスレッドのメモリ アクセスは、この読み込みの前に並べ替えることができません。

これは、コンパイラ コード生成の経験則のようなものです。

しかし、それは C++ の公理ではありません。

多くの場合、簡単に検出できる場合もあれば、より多くの作業が必要な場合もあり、V 上のメモリ Op に対する操作が、A に対するアトミック操作 X で証明可能に並べ替えられる可能性があります。

最も明白な 2 つのケース:

  • V が厳密にローカル変数である場合: そのアドレスは関数の外部では使用できないため、他のスレッド (またはシグナル ハンドラー) からはアクセスできません。
  • A がそのような厳密なローカル変数の場合。

(コンパイラによるこれら 2 つの並べ替えは、X で指定された可能なメモリ順序のいずれにも有効であることに注意してください。)

いずれにせよ、変換は目に見えず、有効なプログラムの可能な実行を変更しません。

これらのタイプのコード変換が有効な場合は、それほど明白ではありません。工夫されたものもあれば、現実的なものもあります。

この不自然な例は簡単に思いつくことができます。

using namespace std;

static atomic<int> A;

int do_acq() {
  return A.load(memory_order_acquire);
}

void do_rel() {
  A.store(0, memory_order_release);
} // that's all folks for that TU

ノート:

個別にコンパイルされたコードで、オブジェクトに対するすべての操作を確認できるようにするための静的変数の使用。アトミック同期オブジェクトにアクセスする関数は静的ではなく、すべてのプログラムから呼び出すことができます。

同期プリミティブとして、A に対する操作は同期関係を確立します。

  • do_rel()ポイント pXで呼び出すスレッド X
  • do_acq()ポイント pYで呼び出すスレッド Y

do_rel()異なるスレッドでの呼び出しに対応する A の変更 M の明確に定義された順序があります。次のいずれかへの各呼び出しdo_acq():

  • pX_iでの呼び出しの結果を監視し、do_rel()pX_i で X の履歴を取得することでスレッド X と同期します
  • A の初期値を観察する

一方、値は常に 0 であるため、呼び出し元のコードは 0 のみを取得do_acq()し、戻り値から何が起こったかを判断できません。A の変更が既に起こっていることは事前に知ることができますが、事後的にしか知ることはできません。アプリオリな知識は、別の同期操作から得ることができます。アプリオリな知識は、スレッド Y の履歴の一部です。いずれにせよ、取得操作には知識がなく、過去の履歴を追加しません。取得操作の既知の部分は空であり、含まれていたものを確実に取得することはありません。 pY_i でのスレッド Y の過去。したがって、A での取得は無意味であり、最適化することができます。

言い換えると、M のすべての可能な値に対して有効なプログラムは、Y の履歴のdo_acq()最新のもの、つまり A のすべての変更が表示される前のものを見るときに有効でなければなりません。do_rel()そのため、 do_rel() は一般的に何も追加しません:do_rel()一部の実行では冗長ではない synchronize-with を追加できますが、追加する最小のもの Y は何もないため、正しいプログラム、競合状態を持たないプログラム (次のように表現されます:その動作は M に依存します。たとえば、その正確性は M の許容値のサブセットを取得する関数です) から何も取得しないように処理する準備ができている必要がありますdo_rel()。そのため、コンパイラはdo_rel()NOP を作成できます。

[注: 引数の行は、0 を読み取って 0 を格納するすべての RMW 操作に簡単に一般化できるわけではありません。おそらく、acq-rel RMW では機能しません。言い換えれば、acq+rel RMW は、"副作用" のために、個別のロードとストアよりも強力です。]

要約: この特定の例では、メモリ操作がアトミック取得操作に関して上下に移動できるだけでなく、アトミック操作を完全に削除できます。

于 2019-12-14T04:22:50.997 に答える