読み取りバリアと書き込みバリアがあります。バリアを取得し、バリアを解放します。その他(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_release
on 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メモリアーキテクチャは実際にはそれよりもはるかに複雑になる可能性があることに注意してください。ただし、取得/解放に固執することでかなり遠くまで到達できます。