web-dev-qa-db-ja.com

C ++ 11 memory_order_acquireおよびmemory_order_releaseセマンティクス?

http://en.cppreference.com/w/cpp/atomic/memory_order 、およびその他のC++ 11オンライン参照では、memory_order_acquireとmemory_order_releaseを次のように定義します。

  • 取得操作:現在のスレッドの読み取り読み取りは、このロードの前に並べ替えることはできません。
  • リリース操作:現在のスレッドのnowritesは、このストアの後で並べ替えることができます。

これにより、取得後の書き込みをbefore取得操作で実行できるようになりますが、これは私にとっても奇妙に思えます(通常の取得/解放操作のセマンティクスはallメモリ操作)。

同じオンラインソース( http://en.cppreference.com/w/cpp/atomic/atomic_flag )は、C++アトミックと上記の緩和されたメモリオーダリングルールを使用してスピンロックミューテックスを構築できることを示唆しています。

lock mutex: while (lock.test_and_set(std::memory_order_acquire))

unlock mutex: lock.clear(std::memory_order_release);               

このロック/ロック解除の定義では、memory_order_acquire/releaseが実際にこのように定義されている場合(つまり、取得後の書き込みの並べ替えを禁止していない場合)、以下の単純なコードは壊れませんか?

Thread1:
  (0) lock
    (1) x = 1;
    (2) if (x != 1) PANIC
  (3) unlock

Thread2:
  (4) lock
    (5) x = 0;
  (6) unlock

次の実行は可能ですか:(0)ロック、(1)x = 1、(5)x = 0、(2)パニック?私は何を取りこぼしたか?

28
Cedomir Segulja

スピンロックミューテックスの実装は私には問題ないように見えます。 acquirereleaseの定義が完全に間違っていると思います。

これが私が知っている取得/解放整合性モデルの最も明確な説明です: Gharachorloo; Lenoski; Laudon; Gibbons; Gupta; Hennessy:スケーラブルな共有メモリマルチプロセッサでのメモリ整合性とイベント順序Int'l Symp Comp Arch、ISCA(17):15-26、1990、doi 10.1145/325096.325102 。 (doiはACMペイウォールの背後にあります。実際のリンクは、ペイウォールの背後にあるコピーではなくです。)

セクション3.3の条件3.1とそれに付随する図3を見てください。

  • 通常のロードまたはストアアクセスが他のプロセッサに対して実行できるようになる前に、以前のすべての取得アクセスを実行する必要があります。
  • 他のプロセッサに関してリリースアクセスの実行を許可する前に、以前のすべての通常のロードおよびストアアクセスを実行する必要があります。
  • 特別なアクセスは、[順次]相互に一貫しています。

重要なのは、取得とリリースが順次一貫していることです(すべてのスレッドは、取得とリリースが発生した順序にグローバルに同意します)。すべてのスレッドは、取得と特定のスレッドでのリリースの間に発生したことが取得の間に発生したことにグローバルに同意しますとリリースします。ただし、通常のロードとストアafterは、リリースの上に(ハードウェアまたはコンパイラーによって)移動でき、通常のロードとストアbefore取得は、取得後に(ハードウェアまたはコンパイラによって)移動できます。

C++標準 (2012年1月のドラフトへのリンクを使用)では、関連するセクションは1.10(11〜14ページ)です。

happens-beforeの定義は、 Lamport;時間、クロック、および分散システムでのイベントの順序付け)の後にモデル化することを目的としています。 [〜#〜] cacm [〜#〜]、21(7):558-565、1978年7月 。 C++acquiresは、Lamportのreceives、C++releasesは、Lamportのsendsに対応します。 Lamportは、単一スレッド内のイベントのシーケンスに全順序を設定しました。C++では、半順序を許可する必要があります(sequencedのC++定義については、10ページのセクション1.9、段落13〜15を参照してください-before。)それでも、sequenced-beforeの順序は、ほとんど期待どおりです。ステートメントは、プログラムで指定された順序で順序付けられます。セクション1.9、パラグラフ14:「完全な式に関連付けられたすべての値の計算と副作用は、評価される次の完全な式に関連付けられたすべての値の計算と副作用の前にシーケンスされます。」

セクション1.10の要点は、data-race-freeであるプログラムは、プログラムが実行された場合と同じ明確に定義された値を生成するということです。逐次一貫性のあるメモリを備え、コンパイラの並べ替えがないマシン。データの競合がある場合、プログラムには定義されたセマンティクスがまったくありません。データの競合がない場合、コンパイラー(またはマシン)は、逐次一貫性の錯覚に寄与しない操作を並べ替えることができます。

セクション1.10、パラグラフ21(14ページ)は次のように述べています。異なるアクセスAとBのペアがある場合、プログラムはdata-race-freeではありません。オブジェクトXへのスレッド、これらのアクセスの少なくとも1つには副作用があり、AがBの前に発生することも、BがAの前に発生することもありません。

パラグラフ6-20は、起こる前の関係の非常に注意深い定義を与えます。重要な定義はパラグラフ12です:

「評価Aは、次の場合に評価Bの前に発生します。

  • AはBの前にシーケンスされます、または
  • スレッド間はBの前に発生します。」

したがって、取得が前に(同じスレッド内で)他のステートメントとほぼ同じようにシーケンスされる場合、取得はそのステートメントの前に発生するように見える必要があります。 (そのステートメントが書き込みを実行するかどうかを含みます。)

同様に、ほとんどすべてのステートメントがリリース前にシーケンスされている場合(同じスレッド内)、そのステートメントはリリース前に発生しているように見える必要があります。 (そのステートメントが値の計算(読み取り)を行うだけかどうかを含みます。)

コンパイラで他の計算をリリース後からリリース前(または取得前から取得後)に移動できる理由は、これらの操作が特にnotしないという事実は、関係の前にスレッド間が発生します(クリティカルセクションの外にあるため)。それらが競合する場合、セマンティクスは定義されておらず、競合しない場合(共有されていないため)、同期に関していつ発生したかを正確に知ることはできません。

これは非常に長い言い方です。cppreference.comの取得と解放の定義は完全に間違っています。サンプルプログラムにはデータの競合状態がなく、PANICは発生しません。

20
Wandering Logic