66

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

読み取りメモリバリアは、一般に、CPUがバリアの後に要求された読み取りを実行する前に、バリアの前に要求された読み取りを実行したことを確認する方法として説明されます。ただし、この定義を使用すると、古い値を読み取ることができるように見えます。つまり、特定の順序で読み取りを実行することは、メインメモリまたは他のCPUを調べて、読み取りバリア時のシステムの最新値を実際に反映したり、その後に書き込みを行ったりする必要があることを意味するわけではありません。読み取りバリア。

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

4

2 に答える 2

135

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

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

書き込みバリアは、書き込みの順序を制御します。メモリへの書き込みは(CPUの速度と比較して)遅いため、通常、書き込みが「実際に発生する」前にポストされる書き込み要求キューがあります。それらは順番にキューに入れられますが、キュー内にある間、書き込みは並べ替えられる場合があります。(したがって、「キュー」は最適な名前ではない可能性があります...)並べ替えを防ぐために書き込みバリアを使用しない限り。

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

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

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

どんなブロック?-ブロックを取得および/または解放します。

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

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

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

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

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

通常、ロックフリープログラミング、または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の誤った値を取得する可能性があります。したがって、write_releaseon foo-> readyを使用します。これは、前述のように、書き込みキューを効果的に「フラッシュ」し(x、y、zがコミットされていることを確認)、ready=true要求をキューに追加します。次に、bar=13リクエストを追加します。リリースバリア(完全ではない)を使用したため、準備が整う前にbar=13が書き込まれる可能性があることに注意してください。しかし、私たちは気にしません!つまり、barが共有データを変更していないと想定しています。

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

また、これは通常、ミューテックス/クリティカルセクションなどのロックとロック解除に使用されるものとまったく同じバリアであることに注意してください。(つまり、lock()で取得し、unlock()で解放します)。

それで、

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

  • 私はあまり馴染みがありませんが、Javaは同じだと思います。通常、read-acquire / write-releaseよりも多くの保証は必要ないため、まったく同じだと思います。

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

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

  • そして最後に、ロックフリープログラミングとCPUメモリアーキテクチャは実際にはそれよりもはるかに複雑になる可能性があることに注意してください。ただし、取得/解放に固執することでかなり遠くまで到達できます。

于 2009-11-24T06:23:46.683 に答える
13

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

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

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

これは、CPU メモリ バリアに関する入門書の 1 つです。

于 2009-11-24T02:59:24.833 に答える