C ++ std :: memory_order_relaxedの混乱

Oct 12 2020

私はC ++メモリバリア(そしてその素晴らしい)についてGCCWikiからこの記事を読んでいました。
私がこの点に到達するまで、それはかなり簡単でした:

反対のアプローチはstd :: memory_order_relaxedです。このモデルでは、発生前の制限を削除することで、同期を大幅に減らすことができます。これらのタイプのアトミック操作では、デッドストアの削除や共通化など、さまざまな最適化を実行することもできます。したがって、前の例では:

-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)

-Thread 2-
if (x.load (memory_order_relaxed) == 10)
  {
    assert (y.load(memory_order_relaxed) == 20) /* assert A */
    y.store (10, memory_order_relaxed)
  }

-Thread 3-
if (y.load (memory_order_relaxed) == 10)
  assert (x.load(memory_order_relaxed) == 10) /* assert B */

スレッドはシステム全体で同期する必要がないため、この例のどちらのアサートも実際には失敗する可能性があります。

わかりました、これも簡単です、続けましょう。

-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)

-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)

アサートは失敗できません。2のストアがスレッド2によって認識されると、値1は認識されなくなります。これにより、エイリアスとなる可能性のある別の参照の緩和された負荷間で1つの変数の緩和された負荷が合体するのを防ぎます。

これが私を混乱させた理由です。スレッド1で順序が同期されていないため、yが値2をロードできず、zが値1をロードできない(そしてアサーションが失敗する)のはなぜですか?

回答

4 NicolBolas Oct 12 2020 at 12:12

緩和された順序は、他のメモリアクセスに関する操作の順序に関連しており、緩和されたアトミックに関連した順序ではありません。最初のケースでは、10が表示されるという事実はx、の値に関して何の意味もありませんy。およびその逆。

ただし、2番目のケースは、同じアトミックオブジェクトに影響を与えるため、異なります。

[intro.races] / 10は、スレッド内で、ある操作が別の操作の前にシーケンスされている場合、その操作は他の操作の「前に行われる」ことを示しています。そして[intro.races] / 14-17は、アトミックに関する次の動作の概要を示しています。

前述の4つのコヒーレンス要件は、両方の操作が緩和された負荷であっても、コンパイラによるアトミック操作の単一オブジェクトへの並べ替えを事実上禁止します。

そして、それはあなたがここに持っているものです。すべての変更は同じオブジェクトに対して行われるため、何らかの順序で行われる必要があります。その順序を正確に決定できない場合でも、順序はコードの「前に起こる」関係を尊重する必要があります。

スレッド1の2つの操作は、「happensbefore」関係によって順序付けられます。そして、スレッド2の操作は、それ自体が「発生前」の関係によって順序付けられます。

それらはすべて同じアトミックオブジェクトに作用しているためy、値2を取得した場合は、「後に発生」xが2に設定されxている必要があります。したがって、アクセスの順序は「x = 1、x = 2」である必要があります。 x "を読んでください。また、の最後の読み取りはxの最初の読み取りの後にx行われるため、取得する値を1にすることはできません。