13

私の知る限り、コンパイラは として宣言されている変数を最適化することはありませんvolatile。ただし、このように宣言された配列があります。

volatile long array[8];

そして、さまざまなスレッドが読み書きします。配列の要素は、いずれかのスレッドによってのみ変更され、他のスレッドによって読み取られます。ただし、特定の状況では、スレッドから要素を変更しても、それを読み取っているスレッドは変更に気付かないことに気付きました。コンパイラがどこかにキャッシュしたかのように、同じ古い値を読み続けます。しかし、コンパイラは原則として揮発性変数をキャッシュすべきではありませんよね? では、どうしてこうなったのでしょう。

:私はvolatileスレッド同期には使用していないので、ロックやアトミック変数を使用するなどの回答をやめてください。揮発性の原子変数とミューテックスの違いを知っています。また、アーキテクチャは x86 であり、プロアクティブなキャッシュ コヒーレンスを備えていることにも注意してください。また、変数が他のスレッドによって変更されたと思われる後、変数を十分に長く読み取ります。長い時間が経過しても、読み取りスレッドは変更された値を見ることができません。

4

10 に答える 10

7

しかし、コンパイラは原則として揮発性変数をキャッシュすべきではありませんよね?

いいえ、原則として、変数を読み書きするたびに、コンパイラは変数のアドレスを読み書きする必要があります。

[編集: 少なくとも、そのアドレスの値が「観測可能」であると実装が信じるまでは、そうしなければなりません。ディートマーが彼の回答で指摘しているように、実装は通常のメモリを「観察できない」と宣言する可能性があります。これは、デバッガー、mprotect、または標準の範囲外のものを使用している人にとっては驚きですが、原則として準拠する可能性があります。]

スレッドをまったく考慮しない C++03 では、スレッドでの実行時に「アドレスへのアクセス」が何を意味するかを定義するのは実装次第です。このような詳細は「メモリ モデル」と呼ばれます。たとえば、pthreads では、揮発性変数を含むメモリ全体をスレッドごとにキャッシュできます。IIRC、MSVC は、適切なサイズの揮発性変数がアトミックであることを保証し、キャッシュを回避します (むしろ、すべてのコアに対して単一のコヒーレント キャッシュまでフラッシュします)。その保証を提供する理由は、Intel でそうするのが合理的に安価だからです。Windows は Intel ベースのアーキテクチャだけを本当に気にかけますが、Posix はよりエキゾチックなものに関心があります。

C++11 は、スレッド化のためのメモリ モデルを定義しており、これはデータ競合であると述べています (つまり、あるスレッドでの読み取りが別のスレッドでの書き込みに対して相対的に順序付けられることを保証しvolatile ません)。2 つのアクセスは、特定の順序で順序付けされるか、不特定の順序で順序付けられます (標準では「不確定な順序」と言うかもしれませんが、思い出せません)、またはまったく順序付けされません。まったくシーケンス化されていないのは悪いことです。2 つのシーケンス化されていないアクセスのいずれかが書き込みである場合、動作は未定義です。

ここで重要なのは、「スレッドから要素を変更し、それを読んでいるスレッドが変更に気付かない」という暗黙の「その後」です。操作が順序付けられていると想定していますが、そうではありません。読み取りスレッドに関する限り、ある種の同期を使用しない限り、他のスレッドでの書き込みがまだ行われていないとは限りません。そして実際にはそれよりも悪い - 私が今書いたことから、指定されていないのは操作の順序だけだと思う​​かもしれませんが、実際にはデータ競合のあるプログラムの動作は未定義です.

于 2012-10-03T14:20:52.637 に答える
4

volatile の機能:

  • 変数が外部ソース (ハードウェア レジスタ、割り込み、別のスレッド、コールバック関数など) から変更された場合、変数の最新の値を保証します。
  • 変数への読み取り/書き込みアクセスのすべての最適化をブロックします。
  • スレッド/割り込み/コールバックがプログラムによって呼び出されたことをコンパイラが認識しない場合に、複数のスレッド/割り込み/コールバック関数間で共有される変数に発生する可能性のある危険な最適化バグを防止します。(これは、さまざまな疑わしい組み込みシステム コンパイラで特に一般的であり、このバグが発生すると、追跡するのが非常に困難になります。)

volatile ではないもの:

  • アトミック アクセスやスレッド セーフを保証するものではありません。
  • ミューテックス/セマフォ/ガード/クリティカル セクションの代わりに使用することはできません。スレッド同期には使用できません。

volatile ができることとできないこと:

  • マルチコア環境での命令キャッシュ/命令パイプ/命令の並べ替えの問題から保護するために、メモリバリアを提供するためにコンパイラによって実装される場合とされない場合があります。コンパイラのドキュメントに明示的に記載されていない限り、volatile がこれを行うと想定しないでください。
于 2012-10-03T14:44:37.957 に答える
3

を使用volatileすると、変数の値を使用するたびに変数を再読み込みすることのみを強制できます。アーキテクチャのさまざまなレベルに存在するさまざまな値/表現が一貫していることを保証するものではありません。

このような保証を得るには、アトミック アクセスとメモリ バリアに関する C11 および C++1 の新しいユーティリティが必要です。多くのコンパイラは、これらを拡張に関して既に実装しています。たとえば、gcc ファミリ (clang、icc など) には、__syncこれらを実装するためのプレフィックスで始まるビルトインがあります。

于 2012-10-03T14:17:48.743 に答える
2

Volatileキーワードは、コンパイラがこの変数にレジスタを使用しないことを保証するだけです。したがって、この変数にアクセスするたびに、メモリの場所が読み取られます。ここで、アーキテクチャ内の複数のプロセッサ間でキャッシュの一貫性があると仮定します。そのため、あるプロセッサが書き込み、別のプロセッサが読み取る場合、通常の状態では表示されるはずです。ただし、コーナー ケースを考慮する必要があります。変数が 1 つのプロセッサ コアのパイプラインにあり、他のプロセッサがそれが書き込まれていると仮定してそれを読み取ろうとしていると仮定すると、問題が発生します。基本的に、共有変数はロックで保護するか、バリアメカニズムを正しく使用して保護する必要があります。

于 2012-10-03T14:22:01.770 に答える
1

このvolatileキーワードは、C++での並行性とはまったく関係ありません。これは、コンパイラが以前の値を使用できないようにするために使用されます。つまり、コンパイラは、コードでアクセスされるたびに値にアクセスするコードを生成します。主な目的は、メモリマップドI/Oのようなものです。ただし、を使用しても、通常のメモリを読み取るときのCPUの動作には影響しません。たとえば、同期ディレクティブがないなどの理由で、CPUがメモリ内で値が変更されたと信じる理由がない場合は、キャッシュからの値を使用できます。 。スレッド間で通信するには、同期が必要です。たとえば、、ロックなどです。volatilevolatilestd::atomic<T>std::mutex

于 2012-10-03T14:25:52.367 に答える
1

C++ の場合:

私の知る限り、コンパイラは揮発性として宣言された変数を最適化しません。

あなたの前提は間違っています。volatileコンパイラへのヒントであり、実際には何も保証しません。コンパイラは、volatile変数の最適化を防ぐことを選択できますが、それだけです。

volatileはロックではありません。そのように使用しないでください。

7.1.5.1

7) [ 注: volatile は、オブジェクトの値が実装によって検出できない方法で変更される可能性があるため、オブジェクトを含む積極的な最適化を回避するための実装へのヒントです。詳細なセマンティクスについては、1.9 を参照してください。一般に、volatile のセマンティクスは、C++ でも C と同じになるように意図されています。

于 2012-10-03T14:12:25.833 に答える
0

volatile 左辺値による C++ アクセスと volatile オブジェクトへの C アクセスは「抽象的に」「観察可能」ですが、実際にはC の動作は C 標準ではなく C++ 標準に従っています。非公式に、volatile宣言は、スレッド内のテキストに関係なく、値が何らかの形で変更される可能性があることをすべてのスレッドに伝えます。スレッドの標準では、同期クリティカルの開始時の同期関数呼び出しによる共有変数を除いて、揮発性かどうか、共有かどうかにかかわらず、オブジェクトの変更を引き起こす別のスレッドによる書き込みの概念はありません。領域。スレッド共有オブジェクトとは無関係です。volatile

あなたのコードがあなたが話しているスレッドと適切に同期していない場合、他のスレッドが書いたものを読んでいるあなたのスレッドは未定義の動作をしています。したがって、コンパイラは必要なコードを生成できます。コードが適切に同期されている場合、他のスレッドによる書き込みはスレッド同期呼び出しでのみ発生します。その必要はありませんvolatile

PS

標準では、「volatile 修飾された型を持つオブジェクトへのアクセスを構成するものは実装定義です」と述べています。したがって、揮発性左辺値の逆参照ごとに読み取りアクセスがある、または 1 つを介したすべての割り当てに対して書き込みアクセスがあると仮定することはできません。

さらに、(「抽象的」)「観察可能な」volatileアクセスが「実際に」明示される方法は、実装によって定義されます。そのため、コンパイラは、定義された抽象アクセスに対応するハードウェア アクセスのコードを生成しない場合があります。たとえば、静的な保存期間と、特別なハードウェアの場所にリンクするための特定のフラグでコンパイルされた外部リンケージを持つオブジェクトのみが、プログラムテキストの外部から変更できるため、他のオブジェクトvolatileは無視されます。

于 2017-02-20T06:49:54.287 に答える
-1

ただし、特定の状況では、スレッドから要素を変更しても、それを読み取っているスレッドは変更に気付かないことに気付きました。コンパイラがどこかにキャッシュしたかのように、同じ古い値を読み続けます。

これは、コンパイラがどこかにキャッシュしたためではなく、読み取りスレッドが CPU コアのキャッシュから読み取ったためであり、書き込みスレッドのキャッシュとは異なる可能性があります。値の変更が CPU コア全体に確実に伝播されるようにするには、適切なメモリ フェンスを使用する必要があります。C++ では、そのために volatile を使用することはできませんし、使用する必要もありません。

于 2012-10-03T14:23:46.643 に答える