48

同僚と私は、x86、x64、Itanium、PowerPC、およびその他の10年前のサーバーCPUで実行されているさまざまなプラットフォーム用のソフトウェアを作成しています。

pthread_mutex_lock()... pthread_mutex_unlock()などのミューテックス関数だけで十分かどうか、または保護された変数を揮発性にする必要があるかどうかについて話し合いました。

int foo::bar()
{
 //...
 //code which may or may not access _protected.
 pthread_mutex_lock(m);
 int ret = _protected;
 pthread_mutex_unlock(m);
 return ret;
}

私の懸念はキャッシングです。コンパイラは_protectedのコピーをスタックまたはレジスタに配置し、その古い値を割り当てに使用できますか?そうでない場合、それが起こらないようにするものは何ですか?このパターンのバリエーションは脆弱ですか?

コンパイラはpthread_mutex_lock()が特殊関数であることを実際には理解していないと思いますが、シーケンスポイントによって保護されているだけですか?

どうもありがとうございました。

更新:わかりました。揮発性が悪い理由を説明する回答のある傾向を見ることができます。私はそれらの答えを尊重しますが、その主題に関する記事はオンラインで簡単に見つけることができます。私がオンラインで見つけることができないもの、そして私がこの質問をしている理由は、私が揮発性なしでどのように保護されているかです。上記のコードが正しければ、キャッシュの問題に対してどのように無防備になりますか?

4

7 に答える 7

15

マルチスレッドには、最も単純な答えはvolatileまったく必要ありません。

長い答えは、クリティカルセクションのようなシーケンスポイントは、使用しているスレッドソリューションと同様にプラットフォームに依存するため、スレッドセーフのほとんどもプラットフォームに依存するということです。

C ++ 0xにはスレッドとスレッドセーフの概念がありますが、現在の標準にはないため、volatile意図されておらず確実に使用できないマルチスレッドプログラミングの操作とメモリアクセスの並べ替えを防ぐものと誤認されることがあります。そのように。

volatileC ++で使用する必要があるのは、メモリマップドデバイスへのアクセスを許可し、との間の変数のsetjmp使用を許可し、シグナルハンドラーlongjmpで変数の使用を許可することだけです。sig_atomic_tキーワード自体は変数をアトミックにしません。

C ++ 0xの朗報std::atomicとして、変数のアトミック操作とスレッドセーフな構成を保証するために使用できるSTL構成があります。選択したコンパイラがそれをサポートするまでは、ブーストライブラリを使用するか、アセンブリコードを無効にして、アトミック変数を提供する独自のオブジェクトを作成する必要があります。

PS多くの混乱は、Javaと.NETが実際にキーワードvolatileC ++を使用してマルチスレッドセマンティクスを適用することによって引き起こされますが、そうでない場合はCに準拠します。

于 2011-07-26T23:15:15.063 に答える
13

スレッドライブラリには、ミューテックスのロックとロック解除に適切なCPUとコンパイラのバリアを含める必要があります。GCCの場合memory、asmステートメントのクローバーはコンパイラーバリアとして機能します。

実際、コードを(コンパイラー)キャッシングから保護するものは2つあります。

  • 純粋でない外部関数(pthread_mutex_*())を呼び出しています。これは、コンパイラがその関数がグローバル変数を変更しないことを認識していないため、それらをリロードする必要があることを意味します。
  • 私が言ったようにpthread_mutex_*()、コンパイラの障壁が含まれています。たとえば、glibc / x86では、クローバーのあるpthread_mutex_lock()マクロを呼び出して、コンパイラに変数を再ロードさせます。lll_lock()memory
于 2011-07-26T23:35:37.977 に答える
10

上記のコードが正しければ、キャッシュの問題に対してどのように無防備になりますか?

C ++ 0xまでは、そうではありません。また、Cでは指定されていません。したがって、実際にはコンパイラに依存します。一般に、コンパイラが、複数のスレッドを含む関数または操作のメモリアクセスの順序の制約を尊重することを保証しない場合、そのコンパイラでマルチスレッドの安全なコードを記述できません。HansJBoehmのスレッドをライブラリとして実装できないを参照してください。

コンパイラがスレッドセーフコードに対してどのような抽象化をサポートする必要があるかについては、メモリバリアに関するウィキペディアのエントリがかなり良い出発点です。

(人々が提案した理由についてはvolatile、一部のコンパイラはコンパイラのメモリバリアとして扱わvolatileれます。これは間違いなく標準ではありません。)

于 2011-07-26T23:54:46.127 に答える
3

volatileキーワードは、割り込みサービスルーチンの一部として変更される可能性のあるメモリマップドハードウェアレジスタなど、変数がプログラムロジックの外部で変更される可能性があることをコンパイラに示すヒントです。これにより、コンパイラはキャッシュされた値が常に正しいと見なすことができなくなり、通常はメモリの読み取りで値を取得するように強制されます。この使用法は、スレッド化より数十年ほど前のものです。シグナルによって操作される変数でも使用されているのを見たことがありますが、使用法が正しいかどうかはわかりません。

ミューテックスによって保護されている変数は、異なるスレッドによって読み取りまたは書き込みが行われたときに正しいことが保証されています。このような変数のビューに一貫性を持たせるには、スレッドAPIが必要です。このアクセスはすべてプログラムロジックの一部であり、volatileキーワードはここでは関係ありません。

于 2011-07-26T23:29:45.833 に答える
2

最も単純なスピンロックアルゴリズムを除いて、ミューテックスコードは非常に複雑です。優れた最適化されたミューテックスロック/ロック解除コードには、優れたプログラマーでさえ理解するのに苦労している種類のコードが含まれています。特別な比較および設定命令を使用し、ロック解除/ロック状態だけでなく待機キューも管理し、オプションでシステムコールを使用して待機状態(ロックの場合)に移行するか、他のスレッドをウェイクアップ(ロック解除の場合)します。

平均的なコンパイラーが(単純なスピンロックを除いて)すべての複雑なコードをデコードして「理解」する方法はありません。したがって、コンパイラーでさえ、ミューテックスが何であるか、そしてそれがどのように関連するかを認識していません。同期するために、実際には、コンパイラがそのようなコードの周りで何かを最適化する方法はありません

これは、コードが「インライン」であるか、クロスモジュール最適化の目的で分析に使用できる場合、またはグローバル最適化が使用できる場合です。

コンパイラはpthread_mutex_lock()が特殊関数であることを実際には理解していないと思いますが、シーケンスポイントによって保護されているだけですか?

コンパイラーはそれが何をするかを知らないので、それを中心に最適化しようとはしません。

どのように「特別」ですか?不透明で、そのように扱われます。不透明な関数の中でも特別なことではありません

他のオブジェクトにアクセスできる任意の不透明な関数と意味上の違いはありません。

私の懸念はキャッシングです。コンパイラは_protectedのコピーをスタックまたはレジスタに配置し、その古い値を割り当てに使用できますか?

はい、コンパイラが従うことができる方法で変数名またはポインタを使用することにより、オブジェクトに透過的かつ直接作用するコードでは。変数を間接的に使用するために任意のポインターを使用する可能性のあるコードではありません。

したがって、不透明な関数の呼び出しの合間にはそうです。渡らない。

また、関数でのみ使用できる変数の場合、名前で:アドレスまたは参照がバインドされていないローカル変数の場合(コンパイラがそれ以降のすべての使用を追跡できないようにするため)。これらは実際、ロック/ロック解除を含む任意の呼び出しにわたって「キャッシュ」することができます。

そうでない場合、それが起こらないようにするものは何ですか?このパターンのバリエーションは脆弱ですか?

関数の不透明度。インライン化されていません。アセンブリコード。システムコール。コードの複雑さ。コンパイラを救済し、「それは複雑なことはただそれを呼び出すだけだ」と考えるすべてのもの。

コンパイラのデフォルトの位置は、 「それを最適化する/私がよく知っているアルゴリズムを書き直そう」ではなく、常に「とにかく何が行われているのかわからない愚かな実行をしよう」です。ほとんどのコードは、複雑な非ローカルな方法で最適化されていません。

ここで、絶対的に悪いと仮定しましょう(コンパイラーが諦めるべきであるという観点から、それは最適化アルゴリズムの観点から絶対的に最良です):

  • 関数は「インライン」(=インライン化に使用可能)です(またはグローバル最適化が開始されるか、すべての関数が道徳的に「インライン」になります)。
  • その同期プリミティブ(ロックまたはロック解除)では、(モノプロセッサのタイムシェアリングシステムやマルチプロセッサの強力に順序付けられたシステムのように)メモリバリアは必要ないため、そのようなものは含まれていません。
  • 特別な命令(比較や設定など)は使用されません(たとえば、スピンロックの場合、ロック解除操作は単純な書き込みです)。
  • スレッドを一時停止またはウェイクアップするためのシステムコールはありません(スピンロックでは必要ありません)。

次に、コンパイラが関数呼び出しを中心に最適化できるため、問題が発生する可能性がありますこれは、他のアクセス可能な変数の「クローバー」を含む空のasmステートメントなどのコンパイラバリアを挿入することで簡単に修正されます。つまり、コンパイラは、呼び出された関数にアクセスできる可能性のあるものはすべて「クローバー」されていると想定しているだけです。

または、保護された変数を揮発性にする必要があるかどうか。

物事を揮発性にする通常の理由で、それを揮発性にすることができます。デバッガーで変数にアクセスできるようにするため、浮動小数点変数が実行時に間違ったデータ型を持つのを防ぐためなどです。

揮発性は本質的に抽象マシンのメモリ操作であり、I / O操作のセマンティクスを持ち、それ自体

  • iostreamのような実際のI/O
  • システムコール
  • その他の不安定な操作
  • asmメモリクローバー(ただし、メモリの副作用はそれらの周りに並べ替えられません)
  • 外部関数の呼び出し(上記のいずれかを実行する可能性があるため)

揮発性メモリの副作用に関しては、揮発性は順序付けられていません。これにより、volatileが先験的に役立つ最も具体的なケース、つまりメモリフェンスが不要な場合でも、スレッドセーフコードを記述するためにvolatileは実質的に役に立たなくなります(実際の使用には役に立たなくなります) 。シングルCPU。(これは、CまたはC ++のいずれかの最も理解されていない側面の1つである可能性があります。)

したがって、volatileは「キャッシング」を防ぎますが、すべての共有変数がvolatileでない限り、volatileはコンパイラによるロック/ロック解除操作の並べ替えを防ぎません

于 2019-11-22T20:55:57.497 に答える
1

ロック/同期プリミティブは、データがレジスタ/ CPUキャッシュにキャッシュされないようにします。つまり、データはメモリに伝播されます。2つのスレッドがロックされた状態でデータにアクセス/変更している場合、データがメモリから読み取られ、メモリに書き込まれることが保証されます。このユースケースでは、揮発性は必要ありません。

ただし、ダブルチェックのあるコードがある場合、コンパイラーはコードを最適化し、冗長なコードを削除して、揮発性が必要になるのを防ぐことができます。

例:シングルトンパターンの例を参照
https://en.m.wikipedia.org/wiki/Singleton_pattern#Lazy_initialization

なぜ誰かがこの種のコードを書くのですか?回答:ロックを発生させないことには、パフォーマンス上の利点があります。

PS:これはスタックオーバーフローに関する私の最初の投稿です。

于 2021-07-14T16:55:50.877 に答える
0

ロックしているオブジェクトが揮発性である場合ではありません。たとえば、それが表す値がプログラムにとって外部の何か(ハードウェアの状態)に依存している場合。 volatileプログラムの実行の結果であるあらゆる種類の動作を示すために使用しないでください。それが実際にvolatile私が個人的に行うことである場合は、基になるオブジェクトではなく、ポインター/アドレスの値をロックします。例えば:

volatile int i = 0;
// ... Later in a thread
// ... Code that may not access anything without a lock
std::uintptr_t ptr_to_lock = &i;
some_lock(ptr_to_lock);
// use i
release_some_lock(ptr_to_lock);

スレッドでオブジェクトを使用するすべてのコードが同じアドレスをロックする場合にのみ機能することに注意してください。したがって、APIの一部である変数を使用してスレッドを使用する場合は、この点に注意してください。

于 2021-07-16T22:59:03.690 に答える