グローバル変数にアクセスする2つのスレッドがある場合、多くのチュートリアルでは、コンパイラーが変数をレジスターにキャッシュしないように変数を揮発性にするため、正しく更新されません。しかし、共有変数にアクセスする2つのスレッドはどちらもmutexを介した保護を必要とするものではありませんか?しかし、その場合、スレッドのロックとミューテックスの解放の間で、コードはその1つのスレッドのみが変数にアクセスできるクリティカルセクションにあります。その場合、変数は揮発性である必要はありません。
したがって、マルチスレッドプログラムでのvolatileの使用/目的は何ですか?
簡単で迅速な回答:volatile
は、プラットフォームに依存しないマルチスレッドアプリケーションプログラミングには(ほとんど)役に立ちません。同期を提供せず、メモリフェンスを作成せず、操作の実行順序を保証しません。操作をアトミックにしません。コードが魔法のようにスレッドセーフになるわけではありません。 volatile
は、すべてのC++で最も誤解されやすい機能です。 volatile
の詳細については、 this 、 this 、および this を参照してください。
一方、volatile
には、それほど明白ではないかもしれない用途があります。 const
を使用するのとほぼ同じ方法で使用でき、保護されていない方法で共有リソースにアクセスする際に間違いを犯している可能性がある場所をコンパイラが示すのに役立ちます。この使用法については、Alexandrescuが この記事 で説明しています。ただし、これは基本的にC++型システムを使用しており、多くの場合は仕掛けと見なされ、未定義の動作を引き起こす可能性があります。
volatile
は、メモリマップドハードウェア、シグナルハンドラ、およびsetjmpマシンコード命令とインターフェイスするときに使用することを特に意図していました。これにより、volatile
は通常のアプリケーションレベルのプログラミングではなく、システムレベルのプログラミングに直接適用できます。
2003 C++標準では、volatile
が変数に対してAcquireセマンティクスまたはリリースセマンティクスを適用するとは言われていません。実際、標準はマルチスレッドのすべての問題について完全に沈黙しています。ただし、特定のプラットフォームでは、volatile
変数にAcquireおよびReleaseセマンティクスを適用します。
現在C++ 11標準doesは、メモリモデルと言語でマルチスレッドを直接認識し、プラットフォームに依存しない方法でそれを処理するライブラリ機能を提供します。ただし、volatile
のセマンティクスは変更されていません。 volatile
はまだ同期メカニズムではありません。 Bjarne Stroustrupは、TCPPPL4Eで次のように述べています。
ハードウェアを直接扱う低レベルのコードを除き、
volatile
を使用しないでください。
volatile
がメモリモデルで特別な意味を持つとは考えないでください。ありません。同期メカニズムではありません(後の言語のように)。同期を取得するには、atomic
、mutex
、またはcondition_variable
。
上記はすべて、2003標準(現在は2011標準)で定義されているC++言語自体に適用されます。ただし、特定のプラットフォームによっては、volatile
の機能に追加の機能または制限が追加されます。たとえば、MSVC 2010では、(少なくとも)セマンティクスの取得と解放doは、volatile
変数に対する特定の操作に適用されます。 MSDNから :
最適化する場合、コンパイラは、揮発性オブジェクトへの参照と他のグローバルオブジェクトへの参照間の順序を維持する必要があります。特に、
揮発性オブジェクトへの書き込み(揮発性書き込み)にはリリースセマンティクスがあります。命令シーケンスの揮発性オブジェクトへの書き込みの前に発生するグローバルまたは静的オブジェクトへの参照は、コンパイルされたバイナリのその揮発性書き込みの前に発生します。
揮発性オブジェクトの読み取り(volatile read)にはAcquireセマンティクスがあります。命令シーケンス内の揮発性メモリの読み取り後に発生するグローバルまたは静的オブジェクトへの参照は、コンパイルされたバイナリの揮発性読み取り後に発生します。
ただし、上記のリンクをたどると、セマンティクスを取得/解放するかどうかについてコメントで議論があることに注意してください実際にこの場合適用されます。
揮発性は、次の理由で時折役立ちます:このコード:
/* global */ bool flag = false;
while (!flag) {}
gccによって次のように最適化されます。
if (!flag) { while (true) {} }
フラグが他のスレッドによって書き込まれている場合、これは明らかに正しくありません。この最適化がなければ、同期メカニズムはおそらく機能することに注意してください(他のコードによっては、いくつかのメモリバリアが必要になる場合があります)。
それ以外の場合、volatileキーワードは使用するには奇妙すぎます-揮発性アクセスと不揮発性アクセスの両方についてメモリ順序の保証を提供せず、アトミック操作も提供しません-つまり、無効なレジスタキャッシングを除き、volatileキーワードを使用してもコンパイラからの助けは得られません。
揮発性で、おそらくロックが必要です。
volatileは、値が非同期に変更される可能性があることをオプティマイザーに伝えるため、
volatile bool flag = false;
while (!flag) {
/*do something*/
}
ループのたびにフラグを読み取ります。
最適化をオフにするか、すべての変数を揮発性にすると、プログラムの動作は同じですが遅くなります。 volatileとは、単に「あなたがそれを読んで、それが何を言っているかを知っているかもしれないが、私がそれを読んだと言ったら、それを読んでください」という意味です。
ロックはプログラムの一部です。ところで、セマフォを実装する場合、とりわけセマフォは揮発性でなければなりません。 (試さないでください。難しいです。おそらく、小さなアセンブラーまたは新しいアトミックなものが必要になります。既に実行されています。)