36

C プログラミング言語と、スレッド化ライブラリとしての Pthreads。スレッド間で共有される変数/構造体は揮発性として宣言する必要がありますか? それらがロックによって保護されているかどうか(おそらくバリア)であると仮定します。

pthread POSIX標準はこれについて発言権を持っていますか、これはコンパイラに依存していますか、それともどちらでもありませんか?

編集して追加: 素晴らしい回答をありがとう。しかし、ロックを使用していない場合はどうでしょう。たとえば、バリアを使用している場合はどうなりますか? または、コンペアアンドスワップなどのプリミティブを使用して共有変数を直接かつアトミックに変更するコード...

4

13 に答える 13

27

ロックを使用して変数へのアクセスを制御している限り、変数に volatile は必要ありません。実際、変数に volatile を設定している場合は、おそらくすでに間違っています。

https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/

于 2008-09-16T23:08:52.867 に答える
7

volatile の非常に重要なプロパティの 1 つは、変数が変更されたときにメモリに書き込まれ、アクセスするたびにメモリから再読み取りされることだと思います。ここでの他の回答では、揮発性と同期が混在しています。これ以外のいくつかの回答から、揮発性が同期プリミティブではないことは明らかです(クレジットが必要な場合はクレジット)。

ただし、volatile を使用しない限り、コンパイラは共有データをレジスタに自由にキャッシュできます。コンパイラの裁量で、揮発性としてマークする必要があります。または、共有データを変更する関数を終了した後にのみ共有データにアクセスする場合は、問題ない可能性があります。しかし、値がレジスタからメモリに書き戻されることを確認するために、盲目的な運に頼らないことをお勧めします。

特に、レジスタが豊富なマシン (つまり、x86 ではない) では、変数はレジスタ内に非常に長い期間存在する可能性があり、優れたコンパイラは、構造体の一部または構造体全体をレジスタにキャッシュすることもできます。したがって、揮発性を使用する必要がありますが、パフォーマンスのために、計算のために値をローカル変数にコピーしてから、明示的な書き戻しを行います。基本的に、volatile を効率的に使用するということは、C コードでロードストアの考え方を少し行うことを意味します。

いずれにせよ、正しいプログラムを作成するには、OS レベルで提供されるある種の同期メカニズムを積極的に使用する必要があります。

volatile の弱点の例については、http://jakob.engbloms.se/archives/65にある私の Decker のアルゴリズムの例を参照してください。これは、volatile が同期に機能しないことをよく証明しています。

于 2008-10-03T13:12:17.543 に答える
4

キーワード volatile はマルチスレッド プログラミングに適しているという考えが広まっています。

Hans Boehmは、volatile の移植可能な用途は 3 つしかないことを指摘しています。

  • volatileを使用して、longjmp 全体で値を保持する必要がある setjmp と同じスコープ内のローカル変数をマークすることができます。問題のローカル変数を共有する方法がない場合、アトミック性と順序付けの制約は効果がないため、そのような使用のどの部分が遅くなるかは不明です。(すべての変数を longjmp 全体で保存することを要求することによって、そのような使用のどの部分が遅くなるかは不明ですが、それは別の問題であり、ここでは考慮されません。)
  • volatileは、変数が「外部的に変更される」可能性がある場合に使用できますが、実際には変更はスレッド自体によって同期的にトリガーされます。たとえば、基になるメモリが複数の場所にマップされているためです。
  • volatile sigatomic_tは、制限された方法で、同じスレッド内のシグナル ハンドラーと通信するために使用できます。sigatomic_t ケースの要件を緩和することを検討することもできますが、それはかなり直感に反するようです。

速度のためにマルチスレッドを使用している場合、コードを遅くすることは絶対に望んでいないことです。マルチスレッド プログラミングの場合、volatile が対処すると誤って考えられることが多い 2 つの重要な問題があります。

  • 原子性
  • メモリの一貫性、つまり、別のスレッドから見たスレッドの操作の順序。

まずは(1)から。Volatile は、アトミックな読み取りまたは書き込みを保証しません。たとえば、129 ビット構造の揮発性の読み取りまたは書き込みは、ほとんどの最新のハードウェアではアトミックではありません。32 ビット int の volatile 読み取りまたは書き込みは、ほとんどの最新のハードウェアではアトミックですが、volatile はそれとは何の関係もありません。揮発性がなければアトミックになる可能性があります。原子性はコンパイラの気まぐれです。C または C++ の標準には、アトミックでなければならないという規定はありません。

ここで問題 (2) を考えます。プログラマーは、volatile を volatile アクセスの最適化をオフにするものと考えることがあります。それは実際にはほとんど真実です。しかし、それは揮発性アクセスのみであり、不揮発性アクセスではありません。次のフラグメントを検討してください。

 volatile int Ready;       

    int Message[100];      

    void foo( int i ) {      

        Message[i/10] = 42;      

        Ready = 1;      

    }

マルチスレッド プログラミングで非常に合理的なことをしようとしています。つまり、メッセージを書き込んでから別のスレッドに送信します。もう一方のスレッドは、Ready がゼロ以外になるまで待機してから、Message を読み取ります。gcc 4.0 または icc を使用して "gcc -O2 -S" でこれをコンパイルしてみてください。どちらも Ready へのストアを最初に行うため、i/10 の計算と重複する可能性があります。並べ替えはコンパイラのバグではありません。それはその仕事をしている積極的なオプティマイザです。

解決策は、すべてのメモリ参照を揮発性とマークすることだと思うかもしれません。それはただのばかげたことです。前の引用が言うように、それはあなたのコードを遅くするだけです. 最悪の場合、問題が解決しない可能性があります。コンパイラが参照を並べ替えなくても、ハードウェアが並べ替える可能性があります。この例では、x86 ハードウェアはそれを並べ替えません。Itanium コンパイラは揮発性ストアにメモリ フェンスを挿入するため、Itanium(TM) プロセッサもそうではありません。これは賢い Itanium 拡張です。しかし、Power(TM) のようなチップは再注文します。順序付けに本当に必要なのは、メモリ バリアとも呼ばれるメモリ フェンスです。メモリ フェンスは、フェンスを越えたメモリ操作の並べ替えを防ぎます。場合によっては、一方向の並べ替えを防ぎます。揮発性はメモリ フェンスとは関係ありません。

では、マルチスレッド プログラミングの解決策は何でしょうか? アトミックおよびフェンス セマンティクスを実装するライブラリまたは言語拡張機能を使用します。意図したとおりに使用すると、ライブラリ内の操作によって適切なフェンスが挿入されます。いくつかの例:

  • POSIX スレッド
  • Windows(TM) スレッド
  • OpenMP
  • 未定

Arch Robison (Intel) の記事に基づく

于 2011-11-14T10:21:12.040 に答える
2

私の経験では、いいえ。これらの値に書き込むときに自分自身を適切にミューテックスするか、別のスレッドのアクションに依存するデータにアクセスする必要がある前にスレッドが停止するようにプログラムを構成する必要があります。私のプロジェクト x264 はこの方法を使用しています。スレッドは膨大な量のデータを共有しますが、その大部分はミューテックスを必要としません。これは、読み取り専用であるか、スレッドがデータにアクセスする必要がある前に、データが利用可能になりファイナライズされるまで待機するためです。

ここで、操作がすべて重くインターリーブされているスレッドが多数ある場合 (非常に細かいレベルで互いの出力に依存している場合)、これははるかに困難になる可能性があります。実際、そのような場合、私はスレッド化モデルを再検討して、スレッド間の分離を増やしてよりきれいに実行できるかどうかを確認してください。

于 2008-09-16T23:03:43.080 に答える
2

番号。

VolatileCPUの読み取り/書き込みコマンドとは無関係に変更できるメモリ位置を読み取る場合にのみ必要です。スレッド化の状況では、CPU は各スレッドのメモリへの読み取り/書き込みを完全に制御します。したがって、コンパイラはメモリが一貫していると想定し、CPU 命令を最適化して不要なメモリ アクセスを減らします。

の主な用途volatileは、メモリ マップド I/O へのアクセスです。この場合、基礎となるデバイスは、CPU とは独立してメモリ位置の値を変更できます。この状態で使用しないvolatileと、CPU は新しく更新された値を読み取る代わりに、以前にキャッシュされたメモリ値を使用する場合があります。

于 2009-02-17T23:06:33.523 に答える
1

pthread_lockPOSIX 7 では、 などの関数もメモリを同期することが保証されています。

https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11「4.12 メモリ同期」は次のように述べています。

次の関数は、他のスレッドに対してメモリを同期します。

pthread_barrier_wait()
pthread_cond_broadcast()
pthread_cond_signal()
pthread_cond_timedwait()
pthread_cond_wait()
pthread_create()
pthread_join()
pthread_mutex_lock()
pthread_mutex_timedlock()
pthread_mutex_trylock()
pthread_mutex_unlock()
pthread_spin_lock()
pthread_spin_trylock()
pthread_spin_unlock()
pthread_rwlock_rdlock()
pthread_rwlock_timedrdlock()
pthread_rwlock_timedwrlock()
pthread_rwlock_tryrdlock()
pthread_rwlock_trywrlock()
pthread_rwlock_unlock()
pthread_rwlock_wrlock()
sem_post()
sem_timedwait()
sem_trywait()
sem_wait()
semctl()
semop()
wait()
waitpid()

したがって、変数が と の間で保護されpthread_mutex_lockpthread_mutex_unlockいる場合は、volatile.

関連する質問:

于 2019-11-19T13:54:28.843 に答える
0

揮発性とは、この値を取得または設定するためにメモリに移動する必要があることを意味します。volatile を設定しないと、コンパイルされたコードがデータを長時間レジスタに格納する可能性があります。

これが意味することは、スレッド間で共有する変数を揮発性としてマークする必要があるということです。これにより、1 つのスレッドが値の変更を開始しても、2 番目のスレッドが来て値を読み取ろうとする前にその結果を書き込まないという状況が発生しません。 .

Volatile は、特定の最適化を無効にするコンパイラ ヒントです。コンパイラの出力アセンブリはそれがなくても安全だったかもしれませんが、共有値には常に使用する必要があります。

システムが提供する高価なスレッド同期オブジェクトを使用していない場合、これは特に重要です。たとえば、一連のアトミックな変更で有効に保つことができるデータ構造を持っている場合があります。メモリを割り当てない多くのスタックは、このようなデータ構造の例です。これは、スタックに値を追加してからエンド ポインターを移動したり、エンド ポインターの移動後にスタックから値を削除したりできるためです。このような構造を実装する場合、アトミックな命令が実際にアトミックであることを保証するために volatile が重要になります。

于 2008-09-16T23:02:25.720 に答える
0

揮発性は、あるスレッドが何かを書き込んでから別のスレッドがそれを読み取るまでの間にまったく遅延が必要ない場合にのみ役立ちます。ただし、ある種のロックがなければ、他のスレッドがいつデータを書き込んだかはわかりません。それが最新の可能な値であるということだけです。

単純な値 (さまざまなサイズの int と float) の場合、明示的な同期ポイントが必要ない場合、mutex はやり過ぎかもしれません。ミューテックスや何らかのロックを使用しない場合は、変数を volatile として宣言する必要があります。ミューテックスを使用する場合は、すべて設定されています。

複雑な型の場合、ミューテックスを使用する必要があります。それらに対する操作は非アトミックであるため、ミューテックスなしで半分変更されたバージョンを読み取ることができます。

于 2008-09-16T23:10:14.860 に答える
-1

一部の人々は明らかに、コンパイラが同期呼び出しをメモリ バリアとして扱うと想定しています。「Casey」は、CPU が 1 つだけあると想定しています。

同期プリミティブが外部関数であり、問​​題のシンボルがコンパイル単位 (グローバル名、エクスポートされたポインター、それらを変更する可能性のあるエクスポートされた関数) の外にある場合、コンパイラーはそれら (またはその他の外部関数呼び出し) を外部から見えるすべてのオブジェクトに関するメモリ フェンス。

そうでなければ、あなたは独りです。また、volatile は、コンパイラに正確で高速なコードを生成させるための最良のツールである可能性があります。ただし、一般的には移植性がありません。揮発性が必要な場合、実際に何をするかは、システムとコンパイラに大きく依存します。

于 2010-09-22T15:35:56.437 に答える
-2

スレッド間で共有される変数は、'volatile' として宣言する必要があります。これにより、1 つのスレッドがそのような変数に書き込む場合、(レジスタではなく) メモリに書き込む必要があることがコンパイラに伝えられます。

于 2010-11-02T03:35:29.843 に答える
-2

いいえ。

まず、volatile必要ありません。を使用しない、保証されたマルチスレッド セマンティクスを提供する操作は他にも多数ありますvolatile。これらには、アトミック操作、ミューテックスなどが含まれます。

第二に、volatile十分ではありません。C 標準では、宣言された変数のマルチスレッド動作に関する保証はありませんvolatile

したがって、必要でも十分でもないので、それを使用する意味はあまりありません。

1 つの例外は、マルチスレッド セマンティクスが文書化されている特定のプラットフォーム (Visual Studio など) です。

于 2016-04-13T19:52:14.073 に答える