マルチスレッドでvolatileを使用するのはいつですか?


130

グローバル変数にアクセスするスレッドが2つある場合、多くのチュートリアルでは、変数を揮発性にして、コンパイラーが変数をレジスターにキャッシュしないようにし、正しく更新されないようにします。ただし、共有変数にアクセスする2つのスレッドは、ミューテックスを介して保護を要求するものではありませんか。しかし、その場合、スレッドのロックとミューテックスの解放の間に、コードはその1つのスレッドだけが変数にアクセスできるクリティカルセクションにあります。この場合、変数は揮発性である必要はありませんか?

したがって、マルチスレッドプログラムでのvolatileの使用/目的は何ですか?


3
場合によっては、ミューテックスによる保護を望まない/必要としないことがあります。
ステファンマイ

4
時々、レースコンディションを持つことは良いことですが、そうでないこともあります。この変数をどのように使用していますか?
David Heffernan

3
@David:レースをすることが「いい」時の例を教えてください。
John Dibling 2010

6
@Johnここに行く。いくつかのタスクを処理しているワーカースレッドがあるとします。ワーカースレッドは、タスクが完了するたびにカウンターをインクリメントします。マスタースレッドは定期的にこのカウンターを読み取り、進行状況のニュースでユーザーを更新します。テアリングを回避するためにカウンタが適切に調整されている限り、アクセスを同期する必要はありません。人種はありますが良性です。
David Heffernan

5
@Johnこのコードが実行されるハードウェアは、整列された変数がテアリングの影響を受けないことを保証します。リーダーが読み取るときにワーカーがnをn + 1に更新している場合、リーダーはnまたはn + 1を取得するかどうかを気にしません。進捗状況の報告にのみ使用されるため、重要な決定は行われません。
David Heffernan、

回答:


167

短い回答:( volatileほとんど)プラットフォームに依存しないマルチスレッドアプリケーションプログラミングには役に立たない。同期は行われず、メモリフェンスも作成されず、操作の実行順序も保証されません。操作をアトミックにしません。コードが魔法のようにスレッドセーフになるわけではありません。 volatileすべてのC ++で最も誤解されている機能の1つです。これをこれこれを詳細については、volatile

一方、volatileそれほど明白ではないかもしれないいくつかの用途があります。これはconst、保護されていない方法で共有リソースにアクセスする際にどこが間違っているかをコンパイラーが示すのに役立つのと同じように使用できます。この使用法については、この記事で Alexandrescuが説明しています。ただし、これは基本的にC ++型システムを使用しているため、多くの場合、工夫として見なされ、未定義の動作を引き起こす可能性があります。

volatile特に、メモリマップされたハードウェア、シグナルハンドラー、およびsetjmpマシンコード命令とのインターフェイスを取るときに使用することを目的としていました。これによりvolatile、通常のアプリケーションレベルのプログラミングではなく、システムレベルのプログラミングに直接適用できます。

2003 C ++標準はそれを言わない volatile、変数にいかなる種類の取得または解放のセマンティクス適用される。実際、標準はマルチスレッドのすべての問題について完全に黙っています。ただし、特定のプラットフォームでは、volatile変数に取得および解放のセマンティクスが適用されます。

[C ++ 11の更新]

C ++ 11標準は今アクノリッジメモリモデルとlanuageに直接マルチスレッド、そしてそれは、プラットフォームに依存しない方法でそれに対処するためのライブラリ機能を提供します。ただし、セマンティクスはvolatileまだ変更されていません。 volatileまだ同期メカニズムではありません。Bjarne StroustrupはTCPPPL4Eでも同じように述べています:

使ってはいけません volatileハードウェアを直接処理する低レベルのコード以外で。

volatileメモリモデルで特別な意味があると仮定しないでください。ありません。後の一部の言語のように、同期メカニズムではありません。同期、使用取得するにはatomicmutexまたはAをcondition_variable

[/更新を終了]

上記はすべて、2003年標準(および現在は2011年標準)で定義されているC ++言語自体に適用されます。ただし、特定のプラットフォームによっては、機能や制限が追加されvolatileます。たとえば、MSVC 2010では(少なくとも)取得と解放のセマンティクスvolatile変数の特定の操作に適用されます。 MSDNから

最適化する場合、コンパイラーは、揮発性オブジェクトへの参照と他のグローバルオブジェクトへの参照間の順序を維持する必要があります。特に、

揮発性オブジェクトへの書き込み(揮発性書き込み)にはリリースセマンティクスがあります。命令シーケンスの揮発性オブジェクトへの書き込みの前に発生するグローバルまたは静的オブジェクトへの参照は、コンパイルされたバイナリでの揮発性書き込みの前に発生します。

揮発性オブジェクトの読み取り(揮発性読み取り)には、取得のセマンティクスがあります。命令シーケンスの揮発性メモリの読み取り後に発生するグローバルオブジェクトまたは静的オブジェクトへの参照は、コンパイル済みバイナリの揮発性メモリの読み取り後に発生します。

ただし、上記のリンクをたどると、コメントに獲得/解放のセマンティクスが実際に適用されるかどうかについての議論が含まれていることに注意してください。


19
私の一部は、回答と最初のコメントの降順のトーンのため、これに反対票を投じたいと思っています。「揮発性は役に立たない」は「手動メモリ割り当ては役に立たない」のようなものです。マルチスレッドプログラムをvolatileそれなしで作成できるのは、スレッドvolatileライブラリを実装するために使用していた人々の肩に立っていたからです。
ベンジャクソン

19
@ベンが何かがあなたの信念に挑戦しているからといってそれを軽視することはありません
デビッド

38
@Ben:いいえ、何をよく読んでvolatile、実際にない C ++で。@Johnの発言は正しい、話の終わりです。これは、アプリケーションコードとライブラリコード、または「通常の」と「神のような全知のプログラマ」とは関係ありません。volatileスレッド間の同期には不要であり、役に立たない。スレッド化ライブラリは、に関しては実装できませんvolatile。いずれにしても、プラットフォーム固有の詳細に依存する必要があり、それらに依存する場合は、もう必要ありませんvolatile
10

6
@jalf:「揮発性は不要であり、スレッド間の同期には役に立たない」(あなたが言ったことです)は、「揮発性はマルチスレッドプログラミングには役に立たない」(ジョンが答えで言ったことです)と同じものではありません。あなたは100%正解ですが、Johnには(部分的に)同意しません

4
@GMan:役立つものはすべて、特定の一連の要件または条件下でのみ有用です。Volatileは、厳密な条件のセットの下でマルチスレッドプログラミングに役立ちます(場合によっては、代替案よりも(いくつかの定義では)より優れている場合があります)。あなたは「これを無視する」と言いますが、揮発性がマルチスレッドに役立つ場合は何も無視しません。あなたは私が主張したことのない何かを作りました。はい、揮発性の有用性は限られていますが、実際には存在しますが、同期には有用ではないことは誰でも同意できます。

31

(編集者注:C ++ 11 volatileはこのジョブに適したツールではなく、データ競合UBがあります。UBなしでこれを行うにはstd::atomic<bool>std::memory_order_relaxedロード/ストアで使用します。実際の実装では、と同じようにコンパイルされvolatileます。より詳細な回答と、弱く順序付けされたメモリがこのユースケースの問題である可能性があるというコメントの誤解に対処する:すべての実際のCPUは一貫した共有メモリを持ってvolatileいるため、実際のC ++実装でこれで動作ます。それをしないでください。

コメントの一部の議論は、リラックスしたアトミックよりも強力なもの必要になる他のユースケースについて話しているようです。この回答volatileは、それが順序を与えないことをすでに指摘しています。)


Volatileは、次の理由で時々役立ちます:このコード:

/* global */ bool flag = false;

while (!flag) {}

gccによって次のように最適化されます。

if (!flag) { while (true) {} }

フラグが他のスレッドによって書き込まれた場合、これは明らかに正しくありません。この最適化がなければ、同期メカニズムはおそらく機能することに注意してください(他のコードによっては、いくつかのメモリバリアが必要になる場合があります)。

それ以外の場合、volatileキーワードは奇妙すぎて使用できません-揮発性アクセスと不揮発性アクセスの両方に対するメモリ順序付けの保証は提供されず、アトミック操作も提供されません。 。


4
私が思い出すと、C ++ 0xアトミックは、多くの人々が(誤って)揮発性によって行われていると信じていることを適切に行うことを意図しています。
David Heffernan、

13
volatileメモリアクセスの並べ替えを妨げません。volatileアクセスは互いに関して並べ替えられませんが、非オブジェクトに関する並べ替えについて保証されないvolatileため、基本的にフラグとしても役に立ちません。
jalf

13
@ベン:逆さまに持っていると思います。「揮発性は役に立たない」群衆は、揮発性が並べ替えから保護されないという単純な事実に依存してます。つまり、同期にはまったく役に立たないということです。他のアプローチも同様に役に立たない可能性があります(言及したように、リンク時のコード最適化により、コンパイラーがブラックボックスとして扱うと想定したコードをコンパイラーが覗く可能性があります)が、の欠点を修正しませんvolatile
2010年

15
@jalf:Arch Robinson(このページの他の場所にリンクされています)の記事、10番目のコメント( "Spud"による)を参照してください。基本的に、並べ替えはコードのロジックを変更しません。投稿されたコードはフラグを使用してタスクをキャンセルする(タスクが完了したことを通知するのではなく)ので、タスクがコードの前または後にキャンセルされるかどうかは関係ありません(例:while (work_left) { do_piece_of_work(); if (cancel) break;}キャンセルがループ内で並べ替えられる場合、ロジックはまだ有効です。同じように機能するコードがありました。メインスレッドが終了したい場合、他のスレッドのフラグを設定しますが、そうではありません...

15
...フラグが設定された直後に合理的に発生する限り、他のスレッドが終了する前に作業ループをさらに数回繰り返すかどうかが重要です。もちろん、これは私が考えることができる唯一の使用法であり、どちらかといえばニッチです(そして、揮発性変数に書き込んでも変更が他のスレッドに表示されないプラットフォームでは機能しない可能性がありますが、少なくともx86とx86-64では動作します)。非常に正当な理由なしに実際にそれを行うようにだれにもアドバイスするつもりはありません。「volatileはマルチスレッドコードでは決して役に立たない」などの包括的なステートメントは100%正しくないというだけです。

15

C ++ 11では、通常はvolatileスレッド化に使用せず、MMIOにのみ使用します

しかしTL:DRは、mo_relaxedコヒーレントキャッシュ(つまりすべて)を備えたハードウェア上でアトミックに「機能」します。レジスタに変数を保持しているコンパイラを停止するだけで十分です。 atomic原子性やスレッド間の可視性を作成するためにメモリバリアは必要ありません。現在のスレッドを操作の前後に待機させて、このスレッドの異なる変数へのアクセス間の順序を作成します。 mo_relaxedロード、ストア、またはRMWを行うだけで、バリアは必要ありません。

ロール自分で自分とアトミックについてvolatile(や障壁のためのインラインASM)C ++ 11の前に悪い昔std::atomicvolatile仕事にいくつかのことを取得する唯一の良い方法でした。しかし、それは実装がどのように機能するかについての多くの仮定に依存しており、いかなる標準によっても保証されていませんでした。

たとえば、Linuxカーネルは引き続き独自のハンドリングされたアトミックをvolatileで使用しますが、特定のC実装(GNU C、clang、および多分ICC)のみをサポートします。これは、GNU Cの拡張機能とインラインasmの構文とセマンティクスが原因である場合もありますが、コンパイラの動作に関するいくつかの仮定に依存しているためでもあります。

ほとんどの場合、新しいプロジェクトでは間違った選択です。std::atomic(with std::memory_order_relaxed)を使用して、コンパイラーでと同じ効率的なマシンコードを生成できますvolatileスレッド化の目的std::atomicmo_relaxed廃止さvolatileれました。一部のコンパイラatomic<double>、最適化に失敗したバグを回避することを除いて。)

std::atomicメインストリームコンパイラ(gccやclangなど)の内部実装は、内部で使用するだけではありませvolatile。コンパイラーは、アトミックなロード、ストア、およびRMW組み込み関数を直接公開します。(例えば、「プレーン」オブジェクトで動作するGNU C __atomicビルトイン。)


揮発性は実際に使用可能です(ただし、使用しないでください)

volatileはいえ、exit_nowCPUがどのように動作するか(コヒーレントキャッシュ)、どのようにvolatile動作するかについての共有された仮定のため、実際のCPU上の既存のすべてのC ++実装のフラグ(?)のようなものに実際に使用できます。しかし、それほど多くはなく、推奨されませんこの回答の目的は、既存のCPUとC ++実装が実際にどのように機能するかを説明することです。あなたがそれを気にしないなら、あなたが知る必要があるのは、スレッドstd::atomic化のvolatileためにmo_relaxedが廃止されたことです。

(ISO C ++標準はかなり曖昧であり、volatileアクセスはC ++抽象マシンのルールに従って厳密に評価されるべきであり、最適化されるべきではないと述べています。実際の実装ではC ++アドレス空間をモデル化するためにマシンのメモリアドレス空間を使用するため、つまりvolatile、メモリ内のオブジェクト表現にアクセスするには、読み取りと割り当てをコンパイルして命令をロード/ストアする必要があります。)


別の答えが指摘するように、exit_nowフラグは同期を必要としないスレッド間通信の単純なケースです。配列の内容が準備できていることなどは公開されていません。別のスレッドでの最適化されていないロードによって即座に気づかされるだけのストア。

    // global
    bool exit_now = false;

    // in one thread
    while (!exit_now) { do_stuff; }

    // in another thread, or signal handler in this thread
    exit_now = true;

揮発性またはアトミックがない場合、as-ifルールおよびデータ競合UBがないという仮定により、コンパイラーは、無限ループに入る(または入る)前にフラグを1回だけチェックするasmに最適化できます。これは、実際のコンパイラで実際に起こることです。(そしてdo_stuff、ループは決して終了しないため、通常はそのほとんどを最適化します。そのため、ループに入ると、結果を使用した可能性のあるそれ以降のコードには到達できません)。

 // Optimizing compilers transform the loop into asm like this
    if (!exit_now) {        // check once before entering loop
        while(1) do_stuff;  // infinite loop
    }

マルチスレッド化プログラムは最適化モードでスタックしますが、-O0正常に実行されます。これは、x86-64のGCCでこれがどのように発生するかを示す例です(GCCのasm出力の説明付き)。また、MCUプログラミング-C ++ O2の最適化は、electronics.SEのループ中断しますが、別の例を示しています。

通常、CSEとホイストがグローバル変数を含むループからロードする積極的な最適化が必要です。

C ++ 11以前volatile bool exit_now、これを意図したとおりに機能させる1つの方法でした(通常のC ++実装で)。ただし、C ++ 11では、データレースUBが引き続き適用されるvolatileため、ハードウェアコヒーレントキャッシュを想定している場合でも、ISO規格によってすべての場所での動作が保証されるわけではありません。

幅の広いタイプの場合volatile、ティアリングがないことは保証されないことに注意してください。これはbool通常の実装では問題ではないため、ここではその区別を無視しました。しかし、それvolatileは、緩和されたアトミックと同等ではなく、依然としてデータ競合UBの影響を受ける理由の一部でもあります。

「意図したとおり」とはexit_now、他のスレッドが実際に終了するのを待機しているスレッドを意味するものではないことに注意してください。あるいはexit_now=true、このスレッドで後の操作を続行する前に、揮発性ストアがグローバルに表示されるまで待機します。(atomic<bool>デフォルトでmo_seq_cstは、少なくともseq_cstがロードされる前に待機します。多くのISAでは、ストアの後に完全なバリアを取得します)。

C ++ 11は、同じをコンパイルする非UBの方法を提供します

「実行を続ける」または「今すぐ終了」フラグはstd::atomic<bool> flagmo_relaxed

使用する

  • flag.store(true, std::memory_order_relaxed)
  • while( !flag.load(std::memory_order_relaxed) ) { ... }

から得られるのとまったく同じasm(高価なバリア命令なし)が得られvolatile flagます。

ティアリングなしだけでなく、atomicUBなしで1つのスレッドに格納して別のスレッドにロードする機能も提供するため、コンパイラーはループからロードを引き上げることができません。(データ競合UBがないという前提により、非アトミックな非揮発性オブジェクトに対して積極的な最適化が可能になります。)この機能atomic<T>volatileは、純粋なロードおよび純粋なストアのほとんど同じです。

atomic<T> また作る +=アトミックRMW操作のなどます(一時、操作、別のアトミックストアへのアトミックロードよりも大幅に高価です。アトミックRMWが必要ない場合は、ローカル一時コードでコードを記述してください)。

seq_cstあなたが得るだろうデフォルトの順序でwhile(!flag)、rtrによる順序付けの保証も追加されます。非アトミックアクセス、および他のアトミックアクセス。

(理論的には、ISO C ++標準はアトミックのコンパイル時の最適化を除外していません。しかし、実際に、それが問題にならない場合を制御する方法がないため、コンパイラーはそうではありvolatile atomic<T>ません。コンパイラーが最適化した場合、アトミックの最適化を十分に制御できるため、現時点ではコンパイラーは最適化しません。コンパイラーが冗長std :: atomic書き込みをマージしないのなぜですか?volatile atomic現在のコード でwg21 / p0062を使用しないことをお勧めします。アトミック。)


volatile これは実際のCPUで実際に機能します(ただし、まだ使用していません)

弱い順序付けのメモリモデル(x86以外)でも。しかし、実際には使用せずatomic<T>mo_relaxed代わりに使用してください!! このセクションの目的は、実際のCPUがどのように機能するかについての誤解に対処することであり、正当化することではありませんvolatile。ロックレスコードを記述している場合は、おそらくパフォーマンスを気にします。キャッシュとスレッド間通信のコストを理解することは、通常、優れたパフォーマンスにとって重要です。

実際のCPUには一貫したキャッシュ/共有メモリがあります。1つのコアからのストアがグローバルに可視になると、他のコアは古い値をロードできなくなります。神話プログラマーが Javaの揮発性について説明しているCPUキャッシュについて信じるatomic<T>、seq_cstメモリー順序のC ++に相当するも参照してください。)

私が言うときの負荷を、私はメモリをアクセスするのasm命令を意味します。これはvolatileアクセスが保証するものであり、非アトミック/非揮発性C ++変数の左辺値から右辺値への変換と同じものではありません。(local_tmp = flagまたはwhile(!flag))。

あなたが敗北する必要がある唯一のものは、最初のチェックの後にまったくリロードしないコンパイル時の最適化です。順序付けなしで、各反復でのロード+チェックで十分です。このスレッドとメインスレッド間の同期がなければ、ストアがいつ発生したか、またはロードwrtの順序がいつ起こったのかを説明しても意味がありません。ループ内の他の操作。このスレッドに表示されている場合にのみ重要です。exit_nowフラグが設定されていることを確認したら、終了します。典型的なx86 Xeonのコア間レイテンシは、個別の物理コア間で40ns程度になる可能性があります。


理論的には、コヒーレントキャッシュのないハードウェア上のC ++スレッド

プログラマーがソースコードで明示的なフラッシュを行う必要がない純粋なISO C ++だけで、これがリモートで効率的になる方法はありません。

理論的には、これとは異なるマシンでC ++実装を行うことができます他のコアの他のスレッドから物事を見えるようにするには、コンパイラーが生成した明示的なフラッシュが必要です。(または、読み取りが多分古いコピーを使用しないようにするため)。C ++標準ではこれは不可能ではありませんが、C ++のメモリモデルは、コヒーレントな共有メモリマシンで効率的になるように設計されています。たとえば、C ++標準では、「読み取り-読み取りコヒーレンス」、「書き込み-読み取りコヒーレンス」などについても説明されています。標準の1つの注記では、ハードウェアへの接続も示しています。

http://eel.is/c++draft/intro.races#19

[注:上記の4つの一貫性要件により、両方の操作が緩和されたロードであっても、アトミック操作を単一のオブジェクトに再配列するコンパイラーは事実上許可されません。これにより、ほとんどのハードウェアで提供されるキャッシュコヒーレンス保証が、C ++のアトミック操作で使用できるようになります。—エンドノート]

release自分自身といくつかの選択したアドレス範囲のみをフラッシュするストアのメカニズムはありません。取得ロードがこのリリースストアを見た場合、他のスレッドが何を読みたいのかわからないため、すべてを同期する必要があります。スレッド間で発生前の関係を確立するrelease-sequence。これにより、書き込みスレッドによって行われた以前の非アトミック操作が安全に読み取れるようになります。リリースストア後にさらに書き込みを行わない限り...)または、コンパイラはほんの少しのキャッシュラインだけがフラッシュを必要とすることを証明するために本当に賢くなります

関連:NUMAではmov + mfenceは安全ですか?コヒーレントな共有メモリがないx86システムが存在しないことについて詳しく説明します。また関連:同じ場所へのロード/ストアの詳細については、ARMでのロードとストアの並べ替え

一貫性のない共有メモリを持つクラスターがあると思いますが、それらは単一システムイメージマシンではありません。各コヒーレンシドメインは個別のカーネルを実行するため、単一のC ++プログラムのスレッドを実行することはできません。代わりに、プログラムの個別のインスタンスを実行します(それぞれに独自のアドレススペースがあります。1つのインスタンスのポインターは、他のインスタンスでは無効です)。

それらが明示的なフラッシュを介して互いに通信するようにするには、通常、MPIまたは他のメッセージ受け渡しAPIを使用して、フラッシュする必要があるアドレス範囲をプログラムに指定します。


実際のハードウェアは、std::threadキャッシュの一貫性の境界を越えて実行されません。

いくつかの非対称ARMチップが存在し、物理アドレス空間は共有されていますが内部共有可能なキャッシュドメインはありません。首尾一貫していない。(たとえば、コメントスレッド A8コアとTI Sitara AM335xのようなCortex-M3)。

ただし、これらのコアでは異なるカーネルが実行され、両方のコアでスレッドを実行できる単一のシステムイメージではありません。std::threadコヒーレントキャッシュなしでCPUコア全体でスレッドを実行するC ++実装については知りません。

特にARMの場合、GCCとclangは、すべてのスレッドが同じ内部共有可能ドメインで実行されることを想定してコードを生成します。実際、ARMv7 ISAマニュアルには、

このアーキテクチャ(ARMv7)は、同じオペレーティングシステムまたはハイパーバイザーを使用するすべてのプロセッサが同じ内部共有可能共有可能性ドメインにあることを想定して記述されています

したがって、別々のドメイン間の非コヒーレントな共有メモリは、異なるカーネル下の異なるプロセス間の通信に共有メモリ領域を明示的にシステム固有に使用するためのものにすぎません。

このコンパイラでのdmb ish(内部共有可能バリア)とdmb sy(システム)メモリバリアを使用したコード生成に関するこのCoreCLRの説明も参照してください。

他のISAのC ++実装は、std::thread一貫性のないキャッシュを使用するコア全体で実行されないという主張をします。 そのような実装が存在しないという証拠はありませんが、その可能性は非常に低いようです。そのように機能する特定のエキゾチックなハードウェアを対象としない限り、パフォーマンスについての考えは、すべてのスレッド間のMESIのようなキャッシュコヒーレンシを想定する必要があります。(atomic<T>ただし、正確性を保証する方法で使用することをお勧めします!)


コヒーレントキャッシュはそれを簡単にします

ただし、コヒーレントキャッシュを備えたマルチコアシステムでは、リリースストアを実装することは、明示的なフラッシュを行わずに、このスレッドのストアのコミットをキャッシュに順序付けることを意味します。(https://preshing.com/20120913/acquire-and-release-semantics/およびhttps://preshing.com/20120710/memory-barriers-are-like-source-control-operations/)。(そして、acquire-loadは、他のコアのキャッシュへのアクセスを注文することを意味します)。

メモリバリア命令は、ストアバッファが空になるまで、現在のスレッドのロードやストアをブロックするだけです。それは常にそれ自体で可能な限り速く発生します。メモリバリアはキャッシュコヒーレンスが完了したことを保証しますか?この誤解に対処します)。したがって、順序付けが必要ない場合は、他のスレッドで表示を確認するだけで十分ですmo_relaxed。(そしてそうですがvolatile、そうしないでください。)

プロセッサーへのC / C ++ 11マッピング も参照してください。

おもしろい事実:x86では、すべてのasmストアはリリースストアです。これは、x86メモリモデルが基本的にseq-cstとストアバッファー(ストアフォワーディング付き)であるためです。


半関連re:ストアバッファー、グローバルな可視性、一貫性:C ++ 11はほとんど保証しません。ほとんどの実際のISA(PowerPCを除く)は、他の2つのスレッドによる2つのストアの出現順序にすべてのスレッドが同意できることを保証します。(正式なコンピュータアーキテクチャメモリモデルの用語では、「マルチコピーアトミック」です)。

別の誤解は、他のコアがストアを表示するためにストアバッファーをフラッシュするために、メモリーフェンスasm命令が必要であるということです。実際には、ストアバッファーは常にできるだけ早く自身を排出(L1dキャッシュにコミット)しようとします。そうしないと、いっぱいになって実行が停止します。完全なバリア/フェンスは、ストアバッファーが空になるまで現在のスレッドを停止するので、後のロードは前のストアの後にグローバルな順序で表示されます。

(x86の者に強く命じASMメモリモデル手段があることvolatileのx86に近いあなたを与えてしまうことがありmo_acq_rel、そのコンパイル時を除き、非アトミック変数で並べ替えがまだ発生することがあります。しかし、ほとんどが非X86ので、弱命じたメモリモデルを持っている、volatilerelaxedなどについてですmo_relaxed可能な限り弱い。)


コメントは拡張ディスカッション用ではありません。この会話はチャットに移動さました
Samuel Liew

2
素晴らしい記事。これは、「単一のグローバル共有ブールフラグには、揮発性ではなくアトミックを使用する」という単なるステートメントではなく、まさに私が探していた(すべての事実を与える)ものです。
bernie

2
@bernie:使用しないatomic、キャッシュ内の同じ変数異なる値を持つ別のスレッドにつながる可能性があるという繰り返しの主張に苛立ち、私はこれを書きました。/ facepalm。キャッシュでは、いいえ、CPU レジスタでははい(非原子変数を使用)。CPUはコヒーレントキャッシュを使用します。atomicCPUがどのように機能するかについての誤解が広がったため、SOに関する他の質問が説明で一杯にならないことを願っています。(これはパフォーマンス上の理由から理解するのに役立つことであり、ISO C ++のアトミックルールがそのまま記述されている理由を説明するのにも役立ちます。)
Peter Cordes

-1
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

bool checkValue = false;

int main()
{
    std::thread writer([&](){
            sleep(2);
            checkValue = true;
            std::cout << "Value of checkValue set to " << checkValue << std::endl;
        });

    std::thread reader([&](){
            while(!checkValue);
        });

    writer.join();
    reader.join();
}

かつてvolatileは役に立たないと信じていたインタビュアーが、最適化は問題を引き起こさず、別々のキャッシュラインを持つさまざまなコアを参照していたと私に主張しました(彼が正確に何を参照しているかは本当に理解していませんでした)。しかし、このコードは、g ++で-O3を使用してコンパイルすると(g ++ -O3 thread.cpp -lpthread)、未定義の動作を示します。基本的に、値がwhileチェックの前に設定されている場合は正常に動作し、そうでない場合は、値をフェッチする手間をかけずにループに入ります(実際には他のスレッドによって変更されました)。基本的に、checkValueの値はレジスターに1回だけフェッチされ、最高レベルの最適化では再度チェックされることはないと考えています。フェッチの前にtrueに設定されている場合は正常に動作し、そうでない場合はループに入ります。間違っていたら訂正してください。


4
これは何と関係がありvolatileますか?はい、このコードはUBですが、これもUB volatileです。
デビッドシュワルツ

-2

揮発性で、おそらくロックが必要です。

volatileはオプティマイザに値が非同期で変更できることを伝えます。

volatile bool flag = false;

while (!flag) {
    /*do something*/
}

ループを回るたびにフラグを読み取ります。

最適化をオフにするか、すべての変数を揮発性にすると、プログラムは同じように動作しますが遅くなります。volatileは、「あなたが読んだばかりで、それが何を言っているか知っていることを知っています。

ロックはプログラムの一部です。ところで、セマフォを実装している場合は、とりわけ、それらは揮発性でなければなりません。(試してはいけません。難しいです。おそらく、少しのアセンブラーや新しいアトミックなものが必要ですが、すでに行われています。)


1
しかし、これは、他の応答の同じ例、ビジー待機、したがって回避すべきものではないでしょうか?これが不自然な例である場合、不自然な実例はありますか?
David Preston

7
@Chris:忙しい待機は時々良い解決策です。特に、数クロックサイクルだけ待機する必要がある場合は、スレッドを一時停止するというはるかに重いアプローチよりもオーバーヘッドがはるかに少なくなります。もちろん、他のコメントで述べたように、このような例は、フラグへの読み取り/書き込みが、それが保護するコードに関して並べ替えられず、そのような保証が与えられないと想定しているため、欠陥があります。 、volatileこの場合でも実際には役に立ちません。しかし、忙しい待機は時々役立つテクニックです。
2011年

3
@richardはい、いいえ。前半は正解です。ただし、これは、CPUとコンパイラが揮発性変数を相互に並べ替えることができないことを意味します。揮発性変数Aを読み取ってから、揮発性変数Bを読み取った場合、コンパイラーは、Bの前にAを読み取ることが保証されたコードを発行する必要があります(ただし、CPUの順序変更があっても)。 。それらは、揮発性の読み取り/書き込みの周りで問題なく並べ替えることができます。したがって、プログラムのすべての変数を揮発性にしない限り、興味があるという保証はありません
jalf

2
@ ctrl-alt-delor:それはvolatile「並べ替えなし」が意味するものではありません。ストアがプログラムの順序で(他のスレッドから)グローバルに見えるようになることを期待しています。それはあなたに何atomic<T>を与えるか、memory_order_releaseまたはseq_cstあなたに与えます。ただし、コンパイル時の順序変更がないことを保証するvolatile だけです。各アクセスはプログラムの順序でasmに表示されます。デバイスドライバに役立ちます。また、現在のコア/スレッド上の割り込みハンドラー、デバッガー、またはシグナルハンドラーとの対話には役立ちますが、他のコアとの対話には役立ちません。
Peter Cordes

1
volatile実際には、keep_runningここで行うようにフラグをチェックするのに十分です。実際のCPUには、手動のフラッシュを必要としない一貫したキャッシュが常にあります。しかし、お勧めする理由はありませんvolatile以上atomic<T>ではmo_relaxed、あなたは同じasmを取得します。
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.