web-dev-qa-db-ja.com

読み取りメモリのバリアと揮発性を理解するには

一部の言語では、変数を裏付けるメモリを読み取る前に「メモリバリアの読み取り」を実行すると説明されているvolatile修飾子を提供しています。

読み取りメモリバリアは一般に、CPUがバリアの後に要求された読み取りを実行する前に、バリアの前に要求された読み取りを実行したことを確認する方法として説明されています。ただし、この定義を使用すると、古い値がまだ読み取られる可能性があります。つまり、特定の順序で読み取りを実行しても、メインメモリや他のCPUを調べて、読み取られた後続の値が読み取りバリアの時点でシステム内の最新の値を実際に反映していることを確認したり、バリアを読み取ります。

それで、揮発性は本当に最新の値が読み取られることを本当に保証しますか、または読み取られた値が少なくともバリアの前の読み取りと同じくらい最新であることを保証しますか?それとも他の解釈?この回答の実際的な意味は何ですか?

55
Jason Kresowaty

読み取りバリアと書き込みバリアがあります。バリアを取得し、バリアを解放します。その他(io対メモリなど)。

バリアは、値の「最新の」値または「鮮度」を制御するためのものではありません。それらは、メモリアクセスの相対的な順序を制御するためにあります。

書き込みバリアは、書き込みの順序を制御します。メモリへの書き込みは(CPUの速度と比較して)遅いため、通常、書き込みが「実際に発生する」前に投稿される書き込み要求キューがあります。それらは順番にキューに入れられますが、キュー内では書き込みが再順序付けされる場合があります。 (つまり、 'queue'は最適な名前ではないかもしれません...)並べ替えを防ぐために書き込みバリアを使用しない限り。

読み取りバリアは、読み取りの順序を制御します。投機的実行(CPUが先を見てメモリから早くロードする)のため、および書き込みバッファが存在するため(CPUは、メモリではなく書き込みバッファから値を読み取ります(つまり、CPUはXを書き込んだと見なします) = 5、それからそれを読み戻す理由、それがまだ待機していることを確認してくださいbecome 5(書き込みバッファーで5)読み取りが順不同で発生する可能性があります。

これは、生成されたコードの順序に関してコンパイラが何をしようとするかに関係なく当てはまります。つまり、C++の 'volatile'は、「メモリ」から値を再読み取りするようにコードに出力するようコンパイラーに指示するだけなので、ここでは役に立ちません。それは、CPUに値を読み取る方法/場所(つまり「メモリ」)を指示しないためです。 CPUレベルで多くのものです)。

したがって、読み取り/書き込みバリアは、読み取り/書き込みキューでの並べ替えを防ぐためにブロックを配置します(通常、読み取りはそれほどキューではありませんが、並べ替えの効果は同じです)。

どんな種類のブロック? -ブロックを取得またはリリースします。

Acquire-たとえば、read-acquire(x)は、xの読み取りを読み取りキューに追加しますそしてキューをフラッシュします(実際にはキューをフラッシュしませんが、この前に何も並べ替えないことを示すマーカーを追加します読み取り。これは、キューがフラッシュされたかのようです)。したがって、後で(コード順で)読み取りを並べ替えることができますが、xの読み取りの前にはできません。

リリース-たとえば、write-release(x、5)は最初にキューをフラッシュ(またはマーカー)し、次に書き込み要求を書き込みキューに追加します。そのため、x = 5の後で、以前の書き込みが並べ替えられることはありませんが、x = 5の前に、後の書き込みが並べ替えられることに注意してください。

読み取りと取得、書き込みとリリースをペアにしたことに注意してください。これは一般的なことですが、さまざまな組み合わせが可能です。

取得と解放は、並べ替えが一方向に進むのを止めるだけなので、「ハーフバリア」または「ハーフフェンス」と見なされます。

完全なバリア(または完全なフェンス)は、取得と解放の両方に適用されます。つまり、並べ替えはありません。

通常、ロックフリープログラミング、またはC#またはJava 'volatile'の場合、必要なのは読み取り/取得および書き込み/解放です。

すなわち

_void threadA()
{
   foo->x = 10;
   foo->y = 11;
   foo->z = 12;
   write_release(foo->ready, true);
   bar = 13;
}
void threadB()
{
   w = some_global;
   ready = read_acquire(foo->ready);
   if (ready)
   {
      q = w * foo->x * foo->y * foo->z;
   }
   else
       calculate_pi();
}
_

したがって、まず、これはスレッドをプログラムするための悪い方法です。ロックの方が安全です。しかし、障壁を説明するためだけに...

ThreadA()がfooの書き込みを完了した後、foo-> ready LASTを実際に最後に書き込む必要があります。そうしないと、他のスレッドがfoo-> readyを早期に確認して、x/y/zの誤った値を取得する可能性があります。したがって、foo-> readyで_write_release_を使用します。これは、前述のように、書き込みキューを効果的に「フラッシュ」し(x、y、zが確実にコミットされる)、ready = trueリクエストをキューに追加します。次に、bar = 13リクエストを追加します。リリースバリア(完全ではない)を使用しただけなので、準備ができる前にbar = 13が書き込まれる可能性があることに注意してください。しかし、私たちは気にしません!つまり、バーが共有データを変更していないと想定しています。

ここで、threadB()は、「準備完了」と言ったときに本当に準備ができていることを知る必要があります。したがって、read_acquire(foo->ready)を実行します。この読み取りは読み取りキューに追加され、その後キューがフラッシュされます。 _w = some_global_もまだキューにあることに注意してください。したがって、foo-> readyを読み取ることができますbefore _some_global_。しかし、繰り返しになりますが、重要なデータの一部ではないため、私たちは気にしません。私たちが気にするのはfoo-> x/y/zです。したがって、それらはフラッシュ/マーカーの取得後に読み取りキューに追加され、foo-> readyを読み取った後にのみ読み取られることが保証されます。

また、これは通常、mutex/CriticalSectionなどのロックとロック解除に使用されるものとまったく同じバリアであることにも注意してください。 (つまり、lock()で取得し、unlock()で解放します)。

そう、

  • これ(つまり、取得/解放)は、MSのドキュメントがC#の「揮発性」変数の読み取り/書き込みに対して発生することとまったく同じです(オプションでMS C++の場合、これは非標準です)。 http://msdn.Microsoft.com/en-us/library/aa645755(VS.71).aspx を参照してください。「揮発性読み取りには「取得セマンティクス」があります。つまり、その後に発生するメモリへの参照の前に発生します...」

  • 私はthink Javaは同じですが、あまり馴染みはありません。通常、それ以上の保証は必要ないので、まったく同じだと思います。読み取り取得/書き込みリリース。

  • あなたの質問では、それが本当に相対的な順序についてすべてであると考えるとき、あなたは正しい軌道に乗っていました-逆順の順序がありました(つまり、「読み取られる値は、バリアの前の読み取りと少なくとも同じくらい最新ですか? "-いいえ、バリアの前の読み取りは重要ではありません。バリアの後の読み取りはAFTERの後になることが保証されています(書き込みの場合はその逆)。

  • また、前述のとおり、並べ替えは読み取りと書き込みの両方で発生するため、一方のスレッドでのみバリアを使用し、もう一方のスレッドでは機能しないことに注意してください。つまり、read-acquireがなければ、書き込みリリースでは不十分です。つまり、正しい順序で書き込んでも、読み取りバリアを使用して書き込みバリアに対応していなければ、間違った順序で読み取られる可能性があります。

  • そして最後に、ロックフリーのプログラミングとCPUメモリアーキテクチャは実際にはそれよりもはるかに複雑になる可能性があることに注意してください。

111
tony

ほとんどのプログラミング言語でのvolatileは、実際のCPU読み取りメモリバリアを意味するのではなく、レジスタへのキャッシュを介して読み取りを最適化しないようにコンパイラに指示します。これは、読み取りプロセス/スレッドが「最終的に」値を取得することを意味します。一般的な手法は、ブール値volatileフラグを宣言して、シグナルハンドラーで設定し、メインプログラムループでチェックすることです。

対照的に、CPUメモリバリアは、CPU命令を介して直接提供されるか、特定のアセンブラニーモニック(x86のlock接頭辞など)で暗示され、たとえば、読み取りの順序でハードウェアデバイスと通信するときに使用されます。メモリマップIOレジスタへの書き込みは重要であるか、マルチプロセッシング環境でのメモリアクセスの同期です。

あなたの質問に答えるために-いいえ、メモリバリアは「最新の」値を保証しませんが、保証します 注文 メモリアクセス操作の。これは、たとえば lock-free プログラミングでは重要です。

ここ は、CPUメモリバリアの入門書です。

9