「揮発性」キーワードは何に使用されますか?


130

volatileキーワードに関する記事をいくつか読んだのですが、正しい使い方がわかりませんでした。C#とJavaで何を使用する必要があるか教えていただけますか?


1
volatileの問題の1つは、複数のことを意味することです。ファンキーな最適化を行わないことがコンパイラーに情報であることは、Cの遺産です。また、アクセス時にメモリバリアを使用する必要があること意味します。しかし、ほとんどの場合、それは単にパフォーマンスを犠牲にしたり、人々を混乱させたりします。:P
AnorZaken

回答:


93

C#とJavaの両方で、 "volatile"は、変数の値がプログラム自体のスコープ外で変更される可能性があるため、変数の値をキャッシュしてはならないことをコンパイラーに指示します。コンパイラーは、変数が「その制御外」に変更された場合に問題を引き起こす可能性のある最適化を回避します。


@トム-正式に指摘、サー-そして修正されました。
ウィルA

11
それはそれよりもはるかに微妙です。
トム・ホーティン-10

1
違う。キャッシングを妨げません。私の答えを見てください。
doug65536 2016年

168

この例を考えてみましょう:

int i = 5;
System.out.println(i);

コンパイラはこれを最適化して、次のように5を出力するだけです。

System.out.println(5);

ただし、変更可能な別のスレッドがある場合i、これは間違った動作です。別のスレッドiが6に変更されても、最適化されたバージョンは5を出力します。

volatileキーワードは、最適化とキャッシングを防止し、従って、変数が別のスレッドによって変更することができる場合に有用です。


3
iマークが付けられていても、最適化は引き続き有効であると思いますvolatile。Javaでは、それはすべて発生前の関係についてです。
トム・ホーティン-10

投稿してくれてありがとう、どういうわけか揮発性は変数ロックとの関係がありますか?
Mircea

@Mircea:それは私が何かを揮発性としてマークすることがすべてであると言われたことです:揮発性としてフィールドをマークすることはスレッドが与えられた変数の一貫した値を見ることができるようにいくつかの内部メカニズムを使用しますが、これは上記の答えでは言及されていません...多分誰かがこれを確認できるかどうか?ありがとう
npinti 2010

5
@Sjoerd:私はこの例を理解しているとは思いません。場合はiローカル変数で、他のスレッドはとにかくそれを変更することはできません。フィールドの場合、コンパイラは、それ以外の場合、呼び出しを最適化できませんfinal。私は、フィールドがfinal明示的に宣言されていない場合にフィールドが「見える」と仮定して、コンパイラーが最適化を行うことができないと思います。
polygenelubricants

1
C#とJavaはC ++ではありません。これは正しくありません。それはキャッシングを妨げず、最適化を妨げません。これは、読み取りと取得、およびストアリリースのセマンティクスについてのものであり、これらは弱い順序のメモリアーキテクチャで必要です。それは投機的実行についてです。
doug65536 2016年

40

volatileが変数に対して行うことを理解するには、変数がvolatileでない場合に何が起こるかを理解することが重要です。

  • 変数は不揮発性です

2つのスレッドAとBが不揮発性変数にアクセスしている場合、各スレッドはそのローカルキャッシュに変数のローカルコピーを保持します。スレッドAがローカルキャッシュで行った変更は、スレッドBからは見えません。

  • 変数は揮発性です

変数が揮発性であると宣言されている場合、それは本質的に、スレッドがそのような変数をキャッシュしてはならない、つまり、メインメモリから直接読み取られない限り、スレッドはこれらの変数の値を信頼してはならないことを意味します。

では、変数を揮発性にするのはいつですか?

多くのスレッドがアクセスできる変数があり、プログラムの他のスレッド/プロセス/外部によって値が更新された場合でも、すべてのスレッドがその変数の最新の更新された値を取得するようにする場合。


2
違う。「キャッシングの防止」とは関係ありません。これは、コンパイラ、または投機的実行によるCPUハードウェアによる並べ替えについてです。
doug65536 2016年

37

揮発性フィールドの読み取りは取得セマンティクスを持っています。つまり、揮発性変数からのメモリ読み取りは、後続のメモリ読み取りの前に行われることが保証されます。コンパイラが並べ替えを実行するのをブロックし、ハードウェアがそれを必要とする場合(弱い順序のCPU)、揮発性読み取りの後に発生したが投機的に早期に開始された読み取りをハードウェアがフラッシュするように特別な命令を使用します。ロード獲得の問題とそのリタイアの間に投機的ロードが発生するのを防ぐことにより、それらが最初に発行されるのを防ぎます。

volatileフィールドの書き込みには、リリースのセマンティクスがあります。つまり、揮発性変数へのメモリの書き込みは、以前のメモリへのすべての書き込みが他のプロセッサから見えるようになるまで遅延されることが保証されます。

次の例について考えてみます。

something.foo = new Thing();

場合はfoo、クラスのメンバ変数であり、他のCPUがによって参照されるオブジェクトのインスタンスへのアクセスを持ってsomething、彼らは、値表示される場合がありますfoo変更をする前に、メモリへの書き込みThingコンストラクタがグローバルに見えます!これが「弱い順序のメモリ」の意味です。これは、コンパイラがストアの前のコンストラクタにすべてのストアを持っている場合でも発生する可能性がありますfoo。場合は、fooあるvolatileその後する店舗fooのリリースのセマンティクスを持つことになり、ハードウェア保証は、書き込み前に書き込みのすべてのことにfoo書き込みができるようにする前に、他のプロセッサに見えるfooが発生します。

書き込みがfoo非常にひどく並べ替えられる可能性があるのはなぜですか?キャッシュラインの保持fooがキャッシュ内にあり、コンストラクター内のストアがキャッシュをミスした場合、ストアはキャッシュへの書き込みがミスするよりもはるかに早く完了する可能性があります。

Intelの(ひどい)Itaniumアーキテクチャは、メモリの順序付けが弱かった。元のXBox 360で使用されていたプロセッサは、メモリの順序が弱かったです。非常に人気の高いARMv7-Aを含む多くのARMプロセッサは、メモリの順序が弱くなっています。

ロックのようなものは完全なメモリバリアを実行するため、開発者はこれらのデータレースを見ないことがよくあります。これは、本質的に同時にセマンティクスを取得および解放することと同じです。ロック内のロードは、ロックを取得する前に投機的に実行することはできません。ロックが取得されるまで遅延されます。ロックの解放全体でストアを遅延させることはできません。ロックを解放する命令は、ロック内で行われたすべての書き込みがグローバルに見えるようになるまで遅延されます。

より完全な例は、「ダブルチェックロック」パターンです。このパターンの目的は、オブジェクトを遅延初期化するために常にロックを取得する必要がないようにすることです。

ウィキペディアに引っかかった:

public class MySingleton {
    private static object myLock = new object();
    private static volatile MySingleton mySingleton = null;

    private MySingleton() {
    }

    public static MySingleton GetInstance() {
        if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                    // Write-release semantics are implicitly handled by marking
                    // mySingleton with 'volatile', which inserts the necessary memory
                    // barriers between the constructor call and the write to mySingleton.
                    // The barriers created by the lock are not sufficient because
                    // the object is made visible before the lock is released.
                }
            }
        }
        // The barriers created by the lock are not sufficient because not all threads
        // will acquire the lock. A fence for read-acquire semantics is needed between
        // the test of mySingleton (above) and the use of its contents. This fence
        // is automatically inserted because mySingleton is marked as 'volatile'.
        return mySingleton;
    }
}

この例では、MySingletonコンストラクター内のストアは、ストアtoの前に他のプロセッサーから見えない可能性がありますmySingleton。その場合、mySingletonを覗く他のスレッドはロックを取得せず、コンストラクターへの書き込みを必ずしも取得しません。

volatileキャッシュを防ぐことはありません。それが行うことは、他のプロセッサが「見る」書き込みの順序を保証することです。ストアのリリースは、保留中のすべての書き込みが完了するまでストアを遅らせ、バスサイクルが発行されて、他のプロセッサに関連するラインがキャッシュされた場合にキャッシュラインを破棄/ライトバックするように指示します。負荷取得は、推測された読み取りをフラッシュし、それらが過去の古い値にならないようにします。


良い説明。また、ダブルチェックの良いロックの例です。ただし、キャッシュの側面が心配なので、いつ使用するかはまだわかりません。1つのスレッドのみが書き込み、1つのスレッドのみが読み取りを行うキューの実装を作成する場合、ロックなしで取得でき、頭と尾の「ポインター」を揮発性としてマークできますか?読者とライターの両方が最新の値を確認できるようにしたいと思います。
nickdu

どちらheadtail仮定してからプロデューサーを防ぐために揮発性である必要性tailは変わりません、と仮定してから消費者を防ぐためにhead変更されません。また、headストアがグローバルに表示される前に、キューデータの書き込みがグローバルに表示されるようにするには、揮発性でなければなりませんhead
doug65536 2016年

+1、最新/「最新」などの用語は、残念ながら、単一の正しい値の概念を意味します。実際には、2人の競合他社がまったく同じタイミングでフィニッシュラインを通過できます。CPUでは、2つのコアがまったく同時に書き込みを要求できます。結局のところ、コアは交代で作業を行うわけではありません。マルチコアは無意味になります。グッドマルチスレッドの考え方/デザインは力の低レベル「latestness」にしようとするに焦点を当てるべきではありません-ロックだけにコアを強制するので、本質的に偽の任意の公正性O / W一度に一つのスピーカーを選択する-ではなく、離れてデザインしてみてくださいそのような不自然な概念の必要性。
AnorZaken

34

volatileキーワードは、JavaとC#の両方で異なる意味を持っています。

ジャワ

Java言語仕様

フィールドは揮発性として宣言できます。その場合、Javaメモリモデルは、すべてのスレッドが変数の一貫した値を参照することを保証します。

C#

volatileキーワードのC#リファレンスから:

volatileキーワードは、オペレーティングシステム、ハードウェア、または同時に実行されているスレッドなどによって、プログラム内のフィールドを変更できることを示します。


投稿していただきありがとうございます。Javaではスレッドコンテキストでその変数をロックするように機能します。C#では、変数の値をプログラムから変更できるだけでなく、OSなどの外部要因によって値を変更できます(暗黙のロックはありません)...これらの違いを正しく理解しているかどうか教えてください...
Mircea

Javaの@Mirceaにはロックは含まれていません。揮発性変数の最新の値が使用されることを保証するだけです。
krock

Javaは何らかのメモリバリアを約束しますか、それとも参照を最適化しないことを約束するだけのC ++やC#のようなものですか?
Steven Sudit、2010

メモリバリアは実装の詳細です。Javaが実際に約束しているのは、すべての読み取りで、最新の書き込みによって書き込まれた値が表示されることです。
Stephen C

1
@StevenSuditはい、ハードウェアがバリアまたはロード/取得または保存/解放を必要とする場合、それらの命令を使用します。私の答えを見てください。
doug65536 2016年

9

Javaでは、 "volatile"を使用して、変数が複数のスレッドで同時に使用される可能性があることをJVMに通知するため、特定の一般的な最適化を適用できません。

特に、同じ変数にアクセスする2つのスレッドが同じマシンの別々のCPUで実行されている状況。メモリアクセスはキャッシュアクセスよりも非常に遅いため、CPUが保持するデータを積極的にキャッシュすることは非常に一般的です。これは、データがCPU1で更新された場合、キャッシュがそれ自体をクリアすることを決定するときではなく、すべてのキャッシュを通過してメインメモリに移動する必要があるため、CPU2は更新された値を(再びすべてのキャッシュを無視して)確認できることを意味します。


1

不揮発性のデータを読み取る場合、実行中のスレッドは常に更新された値を取得する場合と取得しない場合があります。ただし、オブジェクトが揮発性の場合、スレッドは常に最新の値を取得します。


1
答えを言い換えることができますか?
Anirudha Gupta

volatileキーワードは、キャッシュされた値ではなく最新の値を提供します。
Subhash Saini

0

揮発性は並行性の問題を解決しています。その値を同期させるため。このキーワードは、主にスレッドで使用されます。複数のスレッドが同じ変数を更新するとき。


1
それが問題を「解決」するとは思わない。それはある状況で役立つツールです。競合状態のようにロックが必要な状況では、volatileに依存しないでください。
スクラッチ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.