26

VolatileRead/VolatileWriteメソッド (Reflector を使用)の実装を調べていますが、何かに戸惑っています。

これは VolatileRead の実装です。

[MethodImpl(MethodImplOptions.NoInlining)]
public static int VolatileRead(ref int address)
{
    int num = address;
    MemoryBarrier();
    return num;
}

「アドレス」の値を読み取った後にメモリバリアが配置されるのはなぜですか? 逆にいいんじゃないの?(値を読み取る前に配置するため、「アドレス」への保留中の書き込みは、実際の読み取りを行うまでに完了します。同じことが、値の割り当ての前にメモリバリアが配置される VolatileWrite にも当てはまります。なぜですか? ? また、これらのメソッドにNoInlining属性があるのはなぜですか? インライン化するとどうなるでしょうか?

4

2 に答える 2

24

と最近まで思っていました。揮発性読み取りは、あなたが思っているものではありません。最新の値を取得することを保証するものではありません。プログラムコードの後半にある読み取りが、この読み取りの前に移動されないようにすることです。それが仕様で保証されていることです。揮発性の書き込みについても同様に、揮発性の書き込みの後に以前の書き込みが移動されないことが保証されています。

このコードを疑っているのはあなただけではありませんが、 Joe Duffy が私よりもうまく説明しています:)

これに対する私の答えは、私をロックフリーコーディングから隔離するように設計された PFX のようなものを使用する以外は、ロックフリーコーディングをあきらめることです。メモリ モデルは私には難しすぎます。専門家に任せて、安全だとわかっているものに固執します。

いつの日か、これを反映するためにスレッドの記事を更新しますが、最初はもっと賢明に議論できるようにする必要があると思います...

(ところで、インライン化されていない部分についてはわかりません。インライン化により、揮発性の読み取り/書き込みの周りで発生することを意図していない他の最適化が導入される可能性があると思いますが、簡単に間違っている可能性があります...)

于 2009-11-20T22:46:37.770 に答える
4

単純化しすぎているのかもしれませんが、並べ替えやキャッシュの一貫性などに関する説明は詳細すぎると思います。

では、なぜ MemoryBarrier は実際の読み取りの後に来るのでしょうか? int の代わりに object を使用する例でこれを説明しようとします。

正しいと思う人もいるかもしれません: スレッド 1 はオブジェクトを作成します (その内部データを初期化します)。スレッド 1 は、オブジェクトを変数に入れます。次に、「フェンスを実行」し、すべてのスレッドが新しい値を認識します。

次に、読み取りは次のようになります。スレッド 2 は「フェンスを実行します」。スレッド 2 は、オブジェクト インスタンスを読み取ります。スレッド 2 は、そのインスタンスのすべての内部データを確実に持っています (フェンスで開始されたため)。

これに関する最大の問題は、スレッド 1 がオブジェクトを作成して初期化することです。スレッド 1 は、オブジェクトを変数に入れます。スレッドがキャッシュをフラッシュする前に、CPU 自体がキャッシュの一部をフラッシュします... 変数のアドレスのみをコミットします (その変数の内容ではありません)。

その時点で、スレッド 2 はすでにキャッシュをフラッシュしていました。したがって、メインメモリからすべてを読み取ることになります。したがって、変数を読み取ります(そこにあります)。次に、コンテンツを読み取ります (存在しません)。

最後に、CPU 1 はフェンスを行う Thread 1 を実行します。


では、揮発性の書き込みと読み取りはどうなるでしょうか。揮発性書き込みは、オブジェクトの内容をすぐにメモリに移動させ (フェンスから開始)、変数を設定します (実際のメモリにすぐに移動しない場合があります)。次に、揮発性読み取りは最初にキャッシュをクリアします。次に、フィールドを読み取ります。フィールドの読み取り時に値を受け取った場合、その参照が指す内容が実際にそこにあることは確実です。


これらのささいなことによって、はい、VolatileWrite(1) を実行しても、別のスレッドがゼロの値を表示する可能性があります。しかし、他のスレッドが (揮発性読み取りを使用して) 値 1 を確認するとすぐに、参照される可能性のある必要な他のすべての項目が既に存在します。古い値 (0 または null) を読み取るときに、必要なものがまだすべて揃っていないことを考えると、単純に進行しない可能性があるため、実際にはそれを伝えることはできません。


キャッシュが 2 回フラッシュされたとしても、正しいパターンは次のようになるという議論を既にいくつか見ました。 MemoryBarrier
- この呼び出しの前に変更された他の変数をフラッシュ します。



読み取りには同じものが必要
です。
MemoryBarrier の後に何かが表示され、既に読み取られている可能性があるため、内容にアクセスするには別の MemoryBarrier を配置する必要があります。

.Net に存在する場合、これらは 2 つの Write-Fence または 2 つの Read-Fence である可能性があります。


私が言ったことすべてについてはわかりません...これは私が得た多くの情報の「編集」であり、VolatileRead と VolatileWrite が逆に見える理由を本当に説明していますが、それらを使用するときに無効な値が読み取られないことも保証します。

于 2012-04-18T19:10:39.820 に答える