プログラマーレベルのC ++ std :: atomicでは何が保証されますか?


9

私はに関するいくつかの記事、講演、stackoverflowの質問を聞いて読みましstd::atomicたが、私はそれをよく理解していることを確認したいと思います。MESI(または派生した)キャッシュコヒーレンシプロトコル、ストアバッファー、キューの無効化などで遅延が発生する可能性があるため、キャッシュライン書き込みの可視性とまだ少し混乱しています。

私はx86がより強力なメモリモデルを持っていることを読みました。キャッシュの無効化が遅れると、x86は開始された操作を元に戻すことができます。しかし、私は今、プラットフォームに関係なく、C ++プログラマーとして何を想定すべきかについてのみ興味があります。

[T1:スレッド1 T2:スレッド2 V1:共有アトミック変数]

std :: atomicは、

(1)変数でデータ競合は発生しません(キャッシュラインへの排他的アクセスのおかげ)。

(2)使用するmemory_orderに応じて、(バリアを使用して)順次整合性が発生することが保証されます(バリアの前、バリアの後、またはその両方)。

(3)T1のアトミック書き込み(V1)の後、T2のアトミックRMW(V1)はコヒーレントになります(そのキャッシュラインはT1に書き込まれた値で更新されます)。

しかし、キャッシュコヒーレンシープライマーの言及として、

これらすべての意味は、デフォルトで、ロードが古いデータをフェッチできることです(対応する無効化リクエストが無効化キューに置かれていた場合)。

それで、次は正しいですか?

(4)std::atomicT2がT1のアトミックwrite(V)の後にアトミックread(V)の「古い」値を読み取らないことを保証しません。

(4)が正しいかどうかの質問:T1のアトミック書き込みが遅延に関係なくキャッシュラインを無効にする場合、アトミックRMW操作がアトミック読み取りではなく無効化が有効になるのをT2が待機しているのはなぜですか?

(4)が間違っている場合の質問:スレッドは実行時に「古くなった」値を読み、「それが見える」のはいつですか?

回答ありがとうございます

アップデート1

だから私はそのとき(3)について間違っていたようです。次のインターリーブを想像してください。最初のV1 = 0の場合:

T1: W(1)
T2:      R(0) M(++) W(1)

この場合、T2のRMWはW(1)の後に完全に発生することが保証されていますが、それでも「古い」値を読み取ることができます(私は間違っていました)。これによると、atomicは完全なキャッシュコヒーレンシを保証せず、順次一貫性のみを保証します。

アップデート2

(5)この例を想像してみてください(x = y = 0でアトミックです):

T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");

私たちが話した内容によると、画面に表示された「msg」を表示しても、T1の後にT2が実行された以上の情報は得られません。したがって、次のいずれかの実行が発生した可能性があります。

  • T1 <T3 <T2
  • T1 <T2 <T3(T3はx = 1を認識していますが、y = 1はまだ認識していません)

そうですか?

(6)スレッドが常に「古い」値を読み取ることができる場合、通常の「公開」シナリオをとったが、一部のデータの準備ができていることを通知するのではなく、反対のこと(データの削除)を行うとどうなりますか?

T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();

T2は、is_enabledがfalseであることがわかるまで、削除されたptrを引き続き使用します。

(7)また、スレッドが「古い」値を読み取る可能性があるという事実は、1つのロックフリーアトミック権限だけではミューテックスを実装できないことを意味しますか?スレッド間の同期メカニズムが必要になります。ロック可能なアトミックが必要ですか?

回答:


3
  1. はい、データ競合はありません
  2. はい、適切なmemory_order値を使用すると、順次一貫性を保証できます
  3. アトミックなread-modify-writeは常に、同じ変数へのアトミックな書き込みの前または後に完全に発生します。
  4. はい、T2はT1のアトミックな書き込み後に変数から古い値を読み取ることができます

アトミックな読み取り-変更-書き込み操作は、その原子性を保証する方法で指定されます。最初の読み取り後、RMW操作の書き込み前に別のスレッドが値に書き込むことができる場合、その操作はアトミックではありません。

スレッドは、発生前に相対順序が保証されている場合を除いて、常に古い値を読み取ることができます。

RMW操作が「古い」値を読み取る場合、それが生成する書き込みが、読み取った値を上書きする他のスレッドからの書き込みの前に見えることを保証します。

たとえば更新

T1は、書き込みをした場合x=1、T2がないx++と、x最初は0、の保存の観点から、選択肢はx以下のとおりです。

  1. T1の書き込みが最初であるため、T1が書き込みx=1、次にT2が読み取りx==1、それを2にインクリメントしx=2、単一のアトミック操作として書き戻します。

  2. T1の書き込みは2番目です。T2が読み取りx==0、1にインクリメントx=1し、単一の操作として書き戻し、次にT1が書き込みますx=1

ただし、これらの2つのスレッド間に他の同期ポイントがない場合、スレッドはメモリにフラッシュされない操作を続行できます。

したがってx=1、T2がまだ読み取りx==0(したがって書き込みx=1)する場合でも、T1はを発行してから、他のものに進むことができます。

他の同期ポイントがある場合はx、それらの同期ポイントが順序を強制するため、最初に変更されたスレッドが明らかになります。

これは、RMW操作から読み取られた値に条件がある場合に最も明白です。

アップデート2

  1. memory_order_seq_cstすべてのアトミック操作に(デフォルト)を使用する場合、この種のことを心配する必要はありません。プログラムの観点から見ると、「msg」が表示された場合、T1が実行され、次にT3、次にT2が実行されました。

他のメモリ順序(特にmemory_order_relaxed)を使用する場合、コード内に他のシナリオが表示されることがあります。

  1. この場合、バグがあります。is_enabledT2がwhileループに入るときにフラグがtrueであるとすると、T2は本体を実行することを決定します。T1はデータを削除し、T2は宙ぶらりんのポインターであるポインターを無視し、未定義の動作が発生します。アトミックは、フラグでのデータ競合を防ぐ以外に、何の助けにもなりませんし、妨げにもなりません。

  2. あなたはできる単一の原子変数とミューテックスを実装します。


@Anthony Wiliamsに迅速に回答していただき、ありがとうございます。「古い」値を読み取るRMWの例で質問を更新しました。この例を見ると、相対順序付けとは何を意味し、T2のW(1)は書き込みの前に表示されるということですか?T2がT1の変更を確認すると、T2のW(1)を読み取らなくなるという意味ですか?
Albert Caldas

したがって、「スレッドが常に古い値を読み取ることができる」ということは、キャッシュの一貫性が保証されないことを意味します(少なくともc ++プログラマーレベルでは)。私のupdate2を見てください。
アルバート・カルダス

今では、言語とハードウェアのメモリモデルにもっと注意を払って、これらすべてを完全に理解する必要があったことがわかりました。どうもありがとう!
Albert Caldas

1

(3)について-使用されるメモリの順序によって異なります。ストアとRMW操作のstd::memory_order_seq_cst両方がを使用する場合、両方の操作は何らかの方法で順序付けられます。つまり、ストアはRMWの前に行われるか、またはその逆になります。ストアがRMWの前に注文された場合、RMW操作は、ストアされた値を「参照」することが保証されます。ストアがRMWの後に注文された場合、RMW操作によって書き込まれた値を上書きします。

より緩和されたメモリ順序を使用する場合、変更は引き続き何らかの方法(変数の変更順序)で順序付けられますが、RMW操作がRMW操作であっても、RMWがストア操作の値を「参照」するかどうかは保証されません順序で後の変数の変更順序で書き込み。

さらに別の記事を読みたい場合は、C / C ++プログラマー向けのメモリモデルを参照できます。


記事をありがとう、私はまだそれを読んでいませんでした。かなり古いものでも、私の考えをまとめるのに役立ちました。
Albert Caldas

1
それを聞いてうれしいです-この記事は私の修士論文から少し拡張され改訂された章です。:-)導入されたC ++ 11のメモリモデルに焦点を当てています。C ++ 14/17で導入された(小さな)変更を反映するように更新する場合があります。改善のためのコメントや提案があれば教えてください!
mpoeter
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.