9

よくわかりません。私の前の質問への回答は、私の仮定を確認しているようです。しかし、ここで述べたように、 volatile は .Net の原子性を保証するには不十分です。MSIL のインクリメントや割り当てなどの操作は、単一のネイティブ OPCODE に直接変換されないか、多くの CPU が同じ RAM の場所に対して同時に読み取りと書き込みを行うことができます。

明確にするために:

  1. 複数の CPU で書き込みと読み取りがアトミックかどうかを知りたいですか?
  2. volatile とは何かを理解しています。しかし、それで十分ですか?他のCPUが書き込んでいる最新の値を取得したい場合、連動させる必要がありますか?
4

7 に答える 7

10

volatileHerb Sutter は最近、ネイティブ C++ での実際の意味 (メモリ アクセスの順序付けと原子性にどのように影響するか) についての記事を書きました。.NET、および Java 環境。それはかなり良い読みです:

于 2009-02-05T18:33:58.813 に答える
6

.NET の volatile は、変数アトミックへのアクセスを行います。

問題は、それだけでは不十分な場合が多いことです。変数を読み取る必要があり、変数が 0 (リソースが空いていることを示す) の場合は、変数を 1 に設定します (リソースがロックされていることを示し、他のスレッドはその変数に近づかないようにする必要があることを示します)。

0 の読み取りはアトミックです。1 の書き込みはアトミックです。しかし、これら 2 つの操作の間に何かが起こる可能性があります。0 を読み取った後、1 を書き込む前に、別のスレッドが飛び込んで 0 を読み取り、1 を書き込む場合があります。

ただし、.NET の volatile は変数へのアクセスの原子性を保証します。複数のアクセスに依存する操作のスレッドセーフは保証されません。(免責事項: C/C++ の volatile はこれを保証するものではありません。ご存じのとおり、はるかに弱く、アトミック性が保証されていると人々が想定しているため、時折バグの原因になります :))

したがって、ロックも使用して、複数の操作を 1 つのスレッドセーフ チャンクとしてグループ化する必要があります。(または、単純な操作の場合Interlocked、.NET での操作でうまくいく場合があります)

于 2009-02-05T18:08:47.650 に答える
5

ここで銃を飛ばしているかもしれませんが、ここで2つの問題を混同しているように思えます.

1 つはアトミック性です。これは、単一の操作 (複数の手順が必要になる場合があります) が別の単一の操作と競合してはならないことを意味します。

もう 1 つはボラティリティです。この値はいつ変化すると予想され、その理由は何ですか。

最初を取る。2 段階の操作で現在の値を読み取り、変更し、書き戻す必要がある場合、この操作全体を単一の CPU 命令に変換できない限り、確実にロックが必要になります。データの単一キャッシュライン。

ただし、2 つ目の問題は、ロックを行っている場合でも、他のスレッドからは何が見えるかということです。

.NETのvolatileフィールドは、任意の時点で変更できることをコンパイラが認識しているフィールドです。シングルスレッドの世界では、変数の変更は命令のシーケンシャル ストリームのある時点で発生するものであるため、コンパイラは変数を変更するコードを追加したとき、または少なくとも外部に呼び出したときを認識します。コードが返されると、呼び出し前と同じ値ではない可能性があるため、変更されている場合とされていない場合があります。

この知識により、コンパイラは、ループまたは同様のコード ブロックの前に、フィールドから値を一度レジスタに持ち上げることができ、その特定のコードのフィールドから値を再読み取りすることはありません。

ただし、マルチスレッドを使用すると、いくつかの問題が発生する可能性があります。1 つのスレッドが値を調整した可能性があり、別のスレッドは、最適化のために、この値が変更されていないことを認識しているため、しばらくの間この値を読み取ることはありません。

volatileしたがって、基本的にコンパイラに、値が必要になるたびにスナップショットを取得する場合を除いて、任意の時点でこれの現在の値があると想定してはならないことを伝えているときに、フィールドにフラグを立てる場合。

ロックは複数ステップの操作を解決し、揮発性はコンパイラーがフィールド値をレジスターにキャッシュする方法を処理し、一緒になってさらに多くの問題を解決します。

また、単一の cpu-instruction で読み取ることができない何かがフィールドに含まれている場合、そのフィールドへの読み取りアクセスもロックする必要がある可能性が高いことに注意してください。

たとえば、32 ビット CPU で 64 ビット値を書き込んでいる場合、その書き込み操作には 2 つのステップが必要であり、別の CPU 上の別のスレッドがステップ 2 の前に 64 ビット値を読み取ることができた場合完了すると、以前の値の半分と新しい値の半分がうまく混ざり合って取得されます。これは、古い値を取得するよりもさらに悪い場合があります。


編集:コメントに答えるにvolatileは、読み取り/書き込み操作の原子性を保証します。これは、ある意味では真実です。volatileキーワードは32ビットより大きいフィールドには適用できず、実際にはフィールドをシングルにするからです。 cpu-instruction 32 ビットと 64 ビットの両方の CPU で読み取り/書き込み可能。はい、値が可能な限りレジスタに保持されるのを防ぎます。

そのため、コメントの一部が間違っており、volatile64 ビット値には適用できません。

volatileまた、読み取り/書き込みの並べ替えに関するセマンティクスがあることに注意してください。

関連情報については、MSDN のドキュメントまたはC# の仕様を参照してください。こちらのセクション 10.5.3.

于 2009-02-05T18:12:27.330 に答える
1

ハードウェア レベルでは、複数の CPU が同じアトミック RAM の場所に同時に書き込むことはできません。アトミック読み取り/書き込み操作のサイズは、CPU アーキテクチャによって異なりますが、通常、32 ビット アーキテクチャでは 1、2、または 4 バイトです。ただし、結果を読み戻そうとすると、その間に別の CPU が同じ RAM ロケーションに書き込みを行った可能性が常にあります。低レベルでは、通常、スピンロックは共有メモリへのアクセスを同期するために使用されます。高水準言語では、このようなメカニズムは、たとえばクリティカル領域と呼ばれることがあります。

volatile 型は、変数が変更されるとすぐにメモリに書き戻されるようにするだけです (値が同じ関数で使用される場合でも)。値が後で同じ関数で再利用される場合、コンパイラは通常、値をできるだけ長く内部レジスタに保持し、すべての変更が終了したとき、または関数が戻ったときに RAM に格納します。揮発性型は、ハードウェア レジスタに書き込む場合や、マルチスレッド システムなどで値を RAM に確実に格納する場合に最も役立ちます。

于 2009-02-05T18:42:11.700 に答える
0

volatile は、複数ステップのプロセスの原子性ではなく、読み取りの発生方法を指定するため、質問は完全には意味がありません。私の車も芝生を刈りませんが、それを妨げないようにしています。:)

于 2009-02-05T18:08:56.463 に答える
0

問題は、変数の値のレジスタベースのキャッシュされたコピーで発生します。

値を読み取るとき、CPU は最初にそれがレジスタにあるかどうかを確認し (高速)、メイン メモリをチェックします (低速)。

Volatile は、値をできるだけ早くメイン メモリにプッシュし、キャッシュされたレジスタ値を信頼しないようにコンパイラに指示します。特定の場合にのみ役立ちます。

単一の op コードの書き込みを探している場合は、Interlocked.Increment 関連のメソッドを使用する必要があります。ただし、単一の安全な命令で実行できることはかなり制限されています。

最も安全で信頼性の高い方法は、lock() を使用することです (Interlocked.* を実行できない場合)。

編集: lock または interlocked.* ステートメント内にある場合、書き込みと読み取りはアトミックです。あなたの質問の条件の下では、揮発性だけでは十分ではありません

于 2009-02-05T18:12:13.303 に答える
-1

Volatile は、コンパイラに何をすべきかを伝えるコンパイラ キーワードです。アトミック性に必要な (本質的に) バス操作に変換されるとは限りません。これは通常、オペレーティング システムに任せられます。

編集:明確にするために、アトミック性を保証したい場合、揮発性は決して十分ではありません。むしろ、それを十分にするかどうかはコンパイラ次第です。

于 2009-02-05T18:08:17.180 に答える