マルチスレッドのCまたはC ++プログラミングでvolatileが役に立たないと見なされるのはなぜですか?


165

私が最近投稿したこの回答で示されいるように、私はvolatileマルチスレッドプログラミングコンテキストでのユーティリティ(またはその欠如)について混乱しているようです。

私の理解はこれです。変数にアクセスするコードの制御の流れの外で変数が変更される可能性がある場合は常に、その変数をとして宣言する必要がありますvolatile。シグナルハンドラー、I / Oレジスター、および別のスレッドによって変更された変数はすべて、このような状況を構成します。

したがって、グローバルintがありfoofoo1つのスレッドによって読み取られ、別のスレッドによって(おそらく適切なマシン命令を使用して)アトミックに設定されている場合、読み取りスレッドは、シグナルハンドラーによって微調整された変数を見るのと同じようにこの状況を認識します。外部ハードウェア条件によって変更されるため、foo宣言する必要がありますvolatile(または、マルチスレッドの状況では、メモリフェンスの負荷でアクセスします。これはおそらくより良い解決策です)。

どのように、どこで私は間違っていますか?


7
volatileが行うことはすべて、コンパイラーがvolatile変数へのアクセスをキャッシュしてはならないということです。そのようなアクセスをシリアル化することについては何も述べられていません。これについては、ここで何度も説明しましたが、何回かわかりません。また、この質問によって、それらの議論に何かが追加されることはないと思います。

4
そして、再び、それに値するものではなく、賛成投票される前にここで何度も質問された質問。これをやめてください。

14
@neil他の質問を検索して見つけましたが、どういうわけか私が見た既存の説明は、なぜ私が間違っていたのかを本当に理解するために必要なものを引き起こしませんでした。この質問はそのような答えを引き出しました。
Michael Ekstrand 2010年

1
CPUが(キャッシュを介して)データをどのように処理するかについての詳細な調査については、rdrop.com
Sassafras_wot

1
@curiousguyこれは「Cの場合ではない」で私が意味したもので、ハードウェアレジスタなどに書き込むために使用でき、Javaで一般的に使用されるようなマルチスレッドには使用されません。
Monstieur 2018年

回答:


213

volatileマルチスレッドのコンテキストでの問題は、必要なすべての保証が提供されないことです。必要なプロパティはいくつかありますが、すべてではないため、1 volatile 人では依存できません。

ただし、残りのプロパティに使用する必要があるプリミティブも、そうしたプリミティブを提供するため、volatile事実上不要です。

共有データへのスレッドセーフアクセスの場合、次の保証が必要です。

  • 読み取り/書き込みは実際に行われます(コンパイラは値をレジスタに格納するのではなく、メインメモリの更新を後で延期します)
  • 並べ替えは行われません。volatile変数をフラグとして使用して、データを読み取る準備ができているかどうかを示すと仮定します。コードでは、データを準備した後にフラグを設定するだけなので、すべて問題なく見えます。しかし、フラグが最初に設定されるように命令が並べ替えられるとどうなりますか?

volatile最初のポイントを保証しません。また、異なる揮発性の読み取り/書き込み間で並べ替えが発生しないことも保証します。すべてのvolatileメモリアクセスは、指定された順序で発生します。volatileI / Oレジスタまたはメモリマップハードウェアの操作を目的とする場合に必要なのはこれvolatileだけですが、オブジェクトが非揮発性データへのアクセスの同期にのみ使用されることが多いマルチスレッドコードでは役に立ちません。これらのアクセスは、それらのアクセスに対して相対的に並べ替えることができvolatileます。

並べ替えを防止するための解決策は、メモリバリアを使用することです。これは、コンパイラとCPUの両方に、この時点でメモリアクセスを並べ替えられないことを示します。揮発性変数アクセスの周りにそのような障壁を配置することで、不揮発性アクセスであっても、揮発性アクセス全体で並べ替えられなくなり、スレッドセーフなコードを記述できるようになります。

しかし、メモリバリアはまた、それが効果的に私たちが作る、それ自体で必要なものがすべて提供し、保留中のすべての書き込みはバリアに到達したときに実行される/読み込みいることを確認しvolatile、不要に。volatile修飾子を完全に削除できます。

C ++ 11以降、アトミック変数(std::atomic<T>)は関連するすべての保証を提供します。


5
@jbcreix:どの「それ」について尋ねていますか?揮発性またはメモリの障壁?いずれにせよ、答えはほとんど同じです。プログラムの観察可能な動作について説明しているため、コンパイラーとCPUの両方のレベルで動作する必要があります。つまり、CPUがすべての順序を変更せず、保証する動作を変更しないようにする必要があります。ただし、メモリバリアは標準のC ++の一部ではないため(移植性がないため)、現在、移植可能なスレッド同期を作成することはできず、使用するにvolatileは十分強力ではありません。
jalf

4
MSDNの例ではこれを行っており、揮発性アクセスを過ぎて命令を並べ替えることはできないと主張しています:msdn.microsoft.com/en-us/library/12a04hfd
v

27
@OJW:しかし、Microsoftのコンパイラはvolatile完全なメモリバリアになるように再定義します(並べ替えを防止します)。これは標準の一部ではないため、移植可能なコードでこの動作に依存することはできません。
2010年

4
@Skizz:いいえ、そこに方程式の「コンパイラマジック」の部分が入ります。メモリバリアは、CPUとコンパイラの両方が理解する必要があります。コンパイラーがメモリー・バリアーのセマンティクスを理解している場合、そのようなトリック(およびバリアー全体での読み取り/書き込みの再配列)を回避することがわかっています。そして幸いにも、コンパイラーメモリー・バリアーのセマンティクスを理解しているので、結局はすべてうまくいきます。:)
jalf

13
@Skizz:スレッド自体は常に、C ++ 11およびC11より前のプラットフォーム依存の拡張機能です。私の知る限り、スレッド拡張機能を提供するすべてのCおよびC ++環境は、「メモリバリア」拡張機能も提供します。とにかく、volatileマルチスレッドプログラミングでは常に役に立たない。(Visual Studioの場合を除き、volatile メモリバリア拡張です。)
Nemo

49

Linuxカーネルのドキュメントでこれを検討することもできます。

Cプログラマーは、変数が現在の実行スレッド外で変更される可能性があることを意味するために、しばしばvolatileと見なしています。その結果、共有データ構造が使用されている場合、カーネルコードでそれを使用したくなることがあります。つまり、揮発性型を一種の簡単なアトミック変数として扱うことが知られていますが、そうではありません。カーネルコードでvolatileを使用することはほとんど正しくありません。このドキュメントはその理由を説明しています。

volatileに関して理解するべき重要な点は、その目的が最適化を抑制することであり、それが本当にしたいことはほとんどないということです。カーネルでは、不要な同時アクセスから共有データ構造を保護する必要がありますが、これは非常に異なるタスクです。不要な同時実行から保護するプロセスにより、最適化関連のほとんどすべての問題がより効率的に回避されます。

揮発性と同様に、データへの同時アクセスを安全にするカーネルプリミティブ(スピンロック、ミューテックス、メモリバリアなど)は、不要な最適化を防ぐように設計されています。それらが適切に使用されている場合は、揮発性も使用する必要はありません。それでもvolatileが必要な場合は、ほぼ間違いなくどこかにコードのバグがあります。適切に作成されたカーネルコードでは、volatileは物事を遅くするためにのみ機能します。

カーネルコードの典型的なブロックを考えてみましょう:

spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);

すべてのコードがロック規則に従っている場合、the_lockが保持されている間、shared_dataの値が予期せず変更されることはありません。そのデータを使用して再生する可能性のある他のコードは、ロックを待機しています。スピンロックプリミティブはメモリバリアとして機能します-それらは明示的にそのように書かれています-つまり、データアクセスはそれらの間で最適化されません。そのため、コンパイラはshared_dataの内容を知っていると考えるかもしれませんが、spin_lock()呼び出しはメモリバリアとして機能するため、知っていることをすべて忘れさせます。そのデータへのアクセスに最適化の問題はありません。

shared_dataがvolatileと宣言された場合でも、ロックは必要です。しかし、他の誰もそれを操作できないことがわかっている場合、コンパイラーはクリティカルセクションのshared_dataへのアクセスを最適化することもできなくなります。ロックが保持されている間、shared_dataは揮発性ではありません。共有データを処理する場合、適切なロックは揮発性を不要にし、潜在的に有害にします。

揮発性ストレージクラスは、もともとメモリマップI / Oレジスタ用でした。カーネル内では、レジスタアクセスもロックで保護する必要がありますが、コンパイラがクリティカルセクション内でレジスタアクセスを「最適化」することも望まないでしょう。ただし、カーネル内では、I / Oメモリアクセスは常にアクセサ関数を介して行われます。ポインタを介してI / Oメモリに直接アクセスすることは好ましくなく、すべてのアーキテクチャで機能するわけではありません。これらのアクセサーは、不要な最適化を防ぐために記述されているため、揮発性は不要です。

揮発性を使用したくなる別の状況は、プロセッサが変数の値をビジー待機している場合です。ビジー待機を実行する正しい方法は次のとおりです。

while (my_variable != what_i_want)
    cpu_relax();

cpu_relax()呼び出しは、CPU消費電力を下げるか、ハイパースレッドツインプロセッサに譲ることができます。また、メモリバリアとしても機能するため、揮発性は不要です。もちろん、多忙な待機は、一般的にそもそも反社会的行為です。

カーネルでvolatileが理にかなっているまれな状況がまだいくつかあります。

  • 上記のアクセサ関数は、直接I / Oメモリアクセスが機能するアーキテクチャでは揮発性を使用する場合があります。基本的に、各アクセサー呼び出しは、それ自体が少し重要なセクションになり、プログラマーが期待どおりにアクセスできるようにします。

  • メモリを変更するインラインアセンブリコードですが、目に見える副作用はありません。GCCによって削除されるリスクがあります。asmステートメントにvolatileキーワードを追加すると、この削除を防ぐことができます。

  • jiffies変数は、参照されるたびに異なる値を持つことができるという点で特別ですが、特別なロックなしで読み取ることができます。したがって、jiffiesは不安定になる可能性がありますが、このタイプの他の変数の追加は強く推奨されません。この点で、Jiffiesは「愚かな遺産」の問題(Linusの言葉)と見なされます。それを修正することは、それが価値があるよりももっと面倒です。

  • I / Oデバイスによって変更される可能性のあるコヒーレントメモリ内のデータ構造へのポインタは、場合によっては、合法的に揮発性である可能性があります。ネットワークアダプターが使用するリングバッファーは、そのアダプターがポインターを変更して、どの記述子が処理されたかを示しますが、このような状況の例です。

ほとんどのコードでは、上記のvolatileの正当化は適用されません。その結果、volatileの使用はバグと見なされる可能性が高く、コードがさらに精査されることになります。volatileを使いたくなった開発者は、一歩下がって、本当に達成しようとしていることについて考えるべきです。


3
@curiousguy:はい。gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Extended-Asm.htmlも参照してください。
セバスチャンマッハ

1
spin_lock()は、通常の関数呼び出しのように見えます。生成されたコードがspin_lock()の前に読み取られ、レジスターに格納されたshared_dataの値を「忘れる」ようにコンパイラーが特別に処理することの特別な点は、値を新しい値で読み取る必要があるためです。 spin_lock()の後のdo_something_on()?
シンコペート2016年

1
@underscore_d私の要点は、関数名spin_lock()からは何か特別なことをすることはわかりません。何が入っているのか分かりません。特に、コンパイラーが後続の読み取りを最適化することを妨げる実装に何があるのか​​わかりません。
シンコペート2016

1
シンコペートには良い点があります。これは本質的に、プログラマーがそれらの「特別な関数」の内部実装を知っているか、少なくともその動作について非常によく理解されている必要があることを意味します。これにより、次のような追加の質問が発生します-これらの特別な関数は標準化され、すべてのアーキテクチャとすべてのコンパイラで同じように動作することが保証されていますか?利用可能なそのような関数のリストはありますか、または少なくとも問題の関数がコードが「最適化されないように」保護されていることを開発者に通知するためにコードコメントを使用する規則がありますか?
JustAMartin 2016

1
@Tuntable:プライベートstaticは、ポインターを介して任意のコードで操作できます。そしてそのアドレスが取られています。おそらく、データフロー分析は、ポインターが決してエスケープしないことを証明することができますが、これは一般に非常に難しい問題であり、プログラムサイズが超線形です。エイリアスが存在しないことを保証する方法がある場合、スピンロックを越えてアクセスを移動することは実際には問題ありません。しかし、エイリアスが存在しない場合volatileも同様に無意味です。いずれの場合も、「本体が見えない関数の呼び出し」の動作は正しくなります。
Ben Voigt 2017

11

私はあなたが間違っているとは思いません-スレッドA以外の値によって値が変更された場合、スレッドAが値の変更を確認できるようにするためにvolatileが必要です。私が理解しているように、volatileは基本的にコンパイラーは、「この変数をレジスターにキャッシュしないでください。代わりに、アクセスのたびにRAMメモリーから常に読み取り/書き込みを行うようにしてください」。

混乱は、揮発性では多くのことを実装するのに十分ではないためです。特に、最新のシステムは複数のレベルのキャッシングを使用し、最新のマルチコアCPUは実行時にいくつかのファンシーな最適化を行い、最新のコンパイラーはコンパイル時にいくつかのファンシーな最適化を実行します。ソースコードを見ただけの場合に期待する順序で注文します。

したがって、volatile変数の「観測された」変更が、予想どおりのタイミングで発生しない可能性があることを覚えている限り、volatileは問題ありません。具体的には、スレッド間で操作を同期または順序付けする方法として揮発性変数を使用しないでください。これは確実に機能しないためです。

個人的には、揮発性フラグの主な(唯一の?)使用は「pleaseGoAwayNow」ブール値としてです。継続的にループするワーカースレッドがある場合は、ループの各反復で揮発性ブール値をチェックし、ブール値がtrueの場合は終了します。次に、メインスレッドはブール値をtrueに設定し、pthread_join()を呼び出してワーカースレッドがなくなるまで待機することで、ワーカースレッドを安全にクリーンアップできます。


2
ブールフラグはおそらく安全ではありません。ワーカーがタスクを完了し、フラグが読み取られるまで(読み取られている場合)フラグがスコープ内にあることをどのように保証しますか?それが信号の仕事です。エイリアス安全とは、コンパイラーが(そして他のすべてのライブラリー関数が)フラグ変数の状態を変更する可能性があることを意味するため、揮発性は、ミューテックスが関与しない場合に単純なスピンロックを実装するのに適していmutex_lockます。
Potatoswatter 2010年

6
明らかに、ワーカースレッドのルーチンの性質が、ブール値を定期的にチェックすることが保証されている場合にのみ機能します。volatile-bool-flagは、volatile-booleanを保持するオブジェクトが破棄される前に常にスレッドシャットダウンシーケンスが発生し、boolの設定後にスレッドシャットダウンシーケンスがpthread_join()を呼び出すため、スコープ内に留まることが保証されています。pthread_join()は、ワーカースレッドがなくなるまでブロックします。特にマルチスレッドと組み合わせて使用​​する場合、信号には独自の問題があります。
Jeremy Friesner、2010年

2
ブール値がtrueになる前にワーカースレッド作業を完了するとは限りません。実際、ブール値がtrueに設定されている場合は、ほぼ確実にワークユニットの真ん中にあります。ただし、メインスレッドは、どの場合でもワーカースレッドが終了するまでpthread_join()内でブロックする以外は何もしないため、ワーカースレッドがいつ作業ユニットを完了するかは関係ありません。したがって、シャットダウンシーケンスの順序は適切です。揮発性ブール(およびその他の共有データ)は、pthread_join()が戻るまで解放されません。また、pthread_join()は、ワーカースレッドがなくなるまで戻りません。
Jeremy Friesner、2010年

10
@ジェレミー、あなたは実際には正しいですが、理論的にはそれでも壊れる可能性があります。2コアシステムでは、1つのコアが常にワーカースレッドを実行しています。他のコアはブール値をtrueに設定します。ただし、ワーカースレッドのコアがその変更を確認できるという保証はありません。つまり、ブール値のチェックを繰り返しても停止しない場合があります。この動作は、c ++ 0x、java、およびc#メモリモデルで許可されています。実際には、ビジースレッドがメモリバリアをどこかに挿入する可能性が最も高いため、これは決して発生しません。その後、ブール値への変更が確認されます。
deft_code

4
POSIXシステムを採用し、リアルタイムスケジューリングポリシーを使用します。システム内のSCHED_FIFO他のプロセス/スレッドよりも静的優先度が高く、十分なコアが完全に可能です。Linuxでは、リアルタイムプロセスがCPU時間の100%を使用できるように指定できます。優先度の高いスレッド/プロセスがない場合、コンテキストスイッチは行われず、I / Oによってブロックされることはありません。しかし要点は、C / C ++ volatileは適切なデータ共有/同期のセマンティクスを強制するためのものではないということです。不正なコードが時々機能する可能性があることを証明するために、特別なケースを探すのは役に立たない練習です。
FooF 2013

7

volatileスピンロックミューテックスの基本構成を実装するには(不十分ではありますが)便利ですが、それ(またはそれ以上の優れた機能)を取得すると、別のは必要なくなりvolatileます。

マルチスレッドプログラミングの一般的な方法は、マシンレベルですべての共有変数を保護するのではなく、プログラムの流れを導くガード変数を導入することです。volatile bool my_shared_flag;あなたの代わりに

pthread_mutex_t flag_guard_mutex; // contains something volatile
bool my_shared_flag;

これは「難しい部分」をカプセル化するだけでなく、基本的に必要です。Cには、ミューテックスの実装に必要なアトミック操作は含まれていません。通常の操作volatileについて追加の保証をするだけです。

今、あなたはこのようなものを持っています:

pthread_mutex_lock( &flag_guard_mutex );
my_local_state = my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

pthread_mutex_lock( &flag_guard_mutex ); // may alter my_shared_flag
my_shared_flag = ! my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

my_shared_flag キャッシュ不可であっても、揮発性である必要はありません。

  1. 別のスレッドがそれにアクセスできます。
  2. それへの参照が(&演算子で)いつか取られている必要があることを意味します。
    • (または参照が包含構造に取られました)
  3. pthread_mutex_lock ライブラリ関数です。
  4. つまり、コンパイラがpthread_mutex_lock何らかの形でその参照を取得したかどうかをコンパイラが判断できないことを意味します。
  5. つまり、コンパイラは共有フラグ変更することを前提とする必要があります。pthread_mutex_lock
  6. したがって、変数はメモリからリロードする必要があります。volatileは、このコンテキストでは意味がありますが、無関係です。

6

あなたの理解は本当に間違っています。

volatile変数が持つプロパティは、「この変数の読み取りと書き込みは、プログラムの認識可能な動作の一部です」です。つまり、このプログラムは機能します(適切なハードウェアが与えられた場合)。

int volatile* reg=IO_MAPPED_REGISTER_ADDRESS;
*reg=1; // turn the fuel on
*reg=2; // ignition
*reg=3; // release
int x=*reg; // fire missiles

問題は、これがスレッドセーフなものに必要なプロパティではないということです。

たとえば、スレッドセーフカウンターは次のようになります(linux-kernelのようなコード、同等のc ++ 0xを知らない)。

atomic_t counter;

...
atomic_inc(&counter);

これはアトミックであり、メモリバリアはありません。必要に応じて追加してください。volatileを追加しても、近くのコードへのアクセスが関連付けられないため、おそらく役に立ちません(たとえば、カウンターがカウントしているリストに要素を追加するなど)。確かに、プログラムの外でカウンタが増加することを確認する必要はありません。最適化も望まれます。

atomic_inc(&counter);
atomic_inc(&counter);

それでも最適化できます

atomically {
  counter+=2;
}

オプティマイザが十分に賢い場合(コードのセマンティクスは変更されません)。


6

並行環境でデータの整合性を保つには、2つの条件を適用する必要があります。

1)原子性、つまり一部のデータをメモリに読み書きすると、そのデータは1回のパスで読み書きされ、コンテキストの切り替えなどによって中断または競合することはありません。

2)一貫性、つまり読み取り/書き込み操作の順序は、複数の並行環境間で同じであるように見える必要があります-スレッド、マシンなど

volatileは上記のどちらにも当てはまりません。具体的には、volatileの動作に関するcまたはc ++標準には、上記のどちらも含まれていません。

一部のコンパイラ(Intel Itaniumコンパイラなど)は、同時アクセスに対して安全な動作の要素を実装しようとするため(つまり、メモリフェンスを確保することにより)、実際にはさらに悪化します。ただし、コンパイラの実装全体に一貫性はなく、さらに標準ではこれを必要としません。最初の実装の。

変数を揮発性としてマークすることは、基本的にキャッシュのパフォーマンスを低下させているため、多くの場合、コードを遅くするだけで、毎回メモリに値をフラッシュすることを意味します。

c#とjava AFAIKは、volatileを1)と2)に準拠させることでこれを修正しますが、c / c ++コンパイラでは同じことが言えないので、基本的には適切だと思います。

主題についてのより詳細な(公平ではありませんが)ディスカッションについては、こちらをお読みください


3
+1-保証された原子性は、私が欠けていたものの別の部分でした。intの読み込みはアトミックであると想定していたため、揮発性が原因で並べ替えを防止し、読み取り側で完全なソリューションを提供しました。ほとんどのアーキテクチャではそれはまともな仮定だと思いますが、それは保証ではありません。
Michael Ekstrand 2010年

メモリへの個別の読み取りおよび書き込みが中断可能で非アトミックになるのはいつですか?何かメリットはありますか?
batbrat

5

comp.programming.threads FAQには Dave Butenhofによる古典的な説明があります。

Q56:共有変数をVOLATILEと宣言する必要がないのはなぜですか?

ただし、コンパイラとスレッドライブラリの両方がそれぞれの仕様を満たしている場合が心配です。適合するCコンパイラは、共有(不揮発性)変数をレジスタにグローバルに割り当てることができ、CPUがスレッドからスレッドに渡されるときに保存および復元されます。各スレッドは、この共有変数に独自のプライベート値を持ちます。これは、共有変数に必要な値ではありません。

コンパイラーが変数とpthread_cond_wait(またはpthread_mutex_lock)関数のそれぞれのスコープについて十分に知っている場合、これはある意味で本当です。実際には、ルーチンが何らかの方法でデータのアドレスにアクセスできるかどうかを知るのが難しいため、ほとんどのコンパイラーは、外部関数の呼び出し全体でグローバルデータのレジスターコピーを保持しようとしません。

つまり、ANSI Cに厳密に(ただし非常に積極的に)準拠するコンパイラは、volatileなしでは複数のスレッドで動作しない可能性があることは事実です。しかし、誰かがそれを修正した方がいいでしょう。POSIXメモリコヒーレンシ保証を提供しないSYSTEM(つまり、実用的には、カーネル、ライブラリ、およびCコンパイラの組み合わせ)は、POSIX標準に準拠していないためです。限目。POSIXはPOSIX同期関数が必要なことだけを必要とするため、システムは正しい動作のために共有変数でvolatileを使用することを要求できません。

つまり、volatileを使用しなかったためにプログラムが壊れた場合、それはバグです。Cのバグ、スレッドライブラリのバグ、カーネルのバグではない可能性があります。しかし、それはシステムのバグであり、それらを修正するには、これらのコンポーネントの1つ以上が機能する必要があります。

揮発性を使用したくない場合は、揮発性を使用するシステムでは、適切な不揮発性変数よりもはるかに高価になるためです。(ANSI Cは各式で揮発性変数の「シーケンスポイント」を必要としますが、POSIXは同期操作でのみそれらを必要とします-計算集中型のスレッド化アプリケーションでは、揮発性を使用してかなり多くのメモリアクティビティが表示され、結局のところ、それはメモリアクティビティです。本当にあなたを遅くします。)

/ --- [Dave Butenhof] ----------------------- [butenhof@zko.dec.com] --- \
| Digital Equipment Corporation 110 Spit Brook Rd ZKO2-3 / Q18 |
| 603.881.2218、FAX 603.881.0120 Nashua NH 03062-2698 |
----------------- [同時実行によるより良い生活] ---------------- /

ブテンホフ氏はこのusenetの投稿で同じことの多くをカバーしています:

「揮発性」の使用は、適切なメモリの可視性またはスレッド間の同期を保証するのに十分ではありません。ミューテックスの使用で十分であり、さまざまな移植性のないマシンコードの代替手段を使用する場合を除いて(または、以前の投稿で説明したように、一般的に適用するのがはるかに難しいPOSIXメモリルールの微妙な影響)、 mutexが必要です。

したがって、Bryanが説明したように、volatileを使用しても、コンパイラーが有用で望ましい最適化を行えないようにするだけで、コードを「スレッドセーフ」にするための助けは何もありません。もちろん、必要なものはすべて「揮発性」として宣言してもかまいません。結局のところ、これは正当なANSI Cストレージ属性です。スレッド同期の問題が解決されるとは期待しないでください。

C ++にも同様に適用できます。


リンクが壊れています。それはあなたが引用したかったものをもはや指し示していないようです。テキストがなければ、その種の意味のない答えです。
jww

3

これは、「volatile」が行っていることのすべてです。「コンパイラー、この変数は、ローカル命令が機能していない場合でも、任意の瞬間(任意のクロック刻み)で変更できます。この値をレジスターにキャッシュしないでください。」

それだ。これは、値が揮発性であることをコンパイラーに通知します。この値は、外部ロジック(別のスレッド、別のプロセス、カーネルなど)によっていつでも変更される可能性があります。それは多かれ少なかれ、本質的にこれまでキャッシュに対して安全ではない値をレジスタに静かにキャッシュするコンパイラの最適化を抑制するためだけに存在します。

「Dr. Dobbs」のような、マルチスレッドプログラミングの万能薬として揮発性を売り込んでいる記事に出くわすかもしれません。彼のアプローチにはまったくメリットがないわけではありませんが、オブジェクトのユーザーにスレッドセーフの責任を負わせるという根本的な欠陥があり、カプセル化の他の違反と同じ問題が発生する傾向があります。


3

私の古いC標準によれば、「volatileで修飾された型を持つオブジェクトへのアクセスを構成するものは実装定義です」。したがって、Cコンパイラの作成者は、「揮発性」が「マルチプロセス環境でのスレッドセーフアクセス」を意味するように選択することできます。しかし、そうではありませんでした。

代わりに、マルチコアマルチプロセス共有メモリ環境でクリティカルセクションをスレッドセーフにするために必要な操作が、新しい実装定義機能として追加されました。また、「揮発性」がマルチプロセス環境でアトミックアクセスとアクセス順序付けを提供するという要件から解放されたコンパイラライターは、実装に依存する「揮発性」セマンティクスよりもコード削減を優先しました。

つまり、重要なコードセクションの周りの「揮発性」セマフォのようなものは、新しいコンパイラを備えた新しいハードウェアでは機能せず、古いコンパイラで古いハードウェアで機能したことがあるかもしれません。


古い例では、低レベルのプログラミングに適した高品質のコンパイラーでプログラムを処理する必要がありました。残念ながら、「最新の」コンパイラは、標準が「揮発性」を有用な方法で処理することを要求していないという事実を、標準が何もしないことを認識するのではなく、そうすることを要求するコードが壊れていることを示していると見なしています。適合しているが役に立たないほどの低品質の実装を禁じる努力。ただし、人気が高まっている低品質だが準拠しているコンパイラを容認しない
supercat

ほとんどのプラットフォームvolatileでは、ハードウェアに依存するがコンパイラに依存しない方法でOSを作成するために何をする必要があるかを認識するのはかなり簡単です。プログラマがvolatile必要に応じて作業を行うのではなく、実装に依存する機能を使用することを要求すると、標準を持つ目的が損なわれます。
スーパーキャット2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.