2012-04-03 12 views
2

レースコンディションと同時書き込みに関する質問があります。レースコンディションとアンロック書き込み

私はオブジェクトの異なるスレッドからアクセスされるクラスを持っています。私は、必要に応じていくつかの値を計算し、結果をキャッシュしたいと思います。パフォーマンス上の理由から、私はむしろロックを使用しないでください(誰かが尋ねる前に - はい、それは私の場合に関連しています)。

これは競合状態を構成します。ただし、オブジェクトはconstであり、変更されません。したがって、異なるスレッドがキャッシュされる値を計算すると、それらは同じであることが保証された私の使用例になります。ロックしないでこれらの値を書き込むことは安全でしょうか?あるいは、広い意味で、ロックすることなく、異なるスレッドから同じ内容をメモリに書き込むことは安全ですか?

書き込まれる値は、bool型とdouble型です。問題のアーキテクチャーはx86とARMです。

EDIT:誰でも入力できるようにしてください。私は最後にキャッシングを伴わない方法を見つけることに決めました。このアプローチは、「ハック」に似ているように見えますが、フラグ変数の使用に問題があります。

+0

私はC++がそれを保証しているとは思っていませんが、少なくとも現代的なハードウェアでは、どのように問題が発生するかを想像するのは難しいです。 –

+0

プリミティブへの各書き込み操作はアトミックなので、多くのスレッドが同じフィールドセットに同時に書き込みを行っている場合は違いはありません。 – Alain

+1

"異なるスレッドがキャッシュされる値を計算すると、使用例が同一であることが保証されます。"これは変です。書き込まれた値が同一であることを保証できる場合は、なぜそれらを同時に書きますか?それ以上は、なぜそれらを複数回書くか? –

答えて

4

あなたが言うように、これは競合状態です。 C++ 11では、技術的にはデータ競合であり、未定義の動作です。値は同じではありません。

コンパイラがそれをサポートしている場合(例えば、最近のgcc、またはGCCまたはMSVC私Just::Threadライブラリーとは)その後、あなたはそれならば--- はPOD構造体であることを仮定して(あなたのデータを中心に、原子ラッパーを提供するために、std::atomic<some_pod_struct>を使用することができますあなたは大きな問題を抱えていません)。それが十分小さい場合、コンパイラはロックフリーにし、適切なアトミック操作を使用します。大きな構造の場合、ライブラリはロックを使用します。

アトミック操作またはロックなしでこれを行う際の問題は、可視性です。これはキャッシュだとすれば、複数のスレッド/プロセッサから同じメモリに同じデータ(実際にはバイトごとに同一であると仮定します)を書き込んだx86またはARMのプロセッサレベルで問題はありませんが、すでに書き込まれている場合は、このデータを再計算するのではなく、このデータを読み込むことをお勧めします。したがって、完了を示すために何らかのフラグが必要です。アトミック操作、ロックまたは適切なメモリバリア命令を使用しない限り、のデータの前に、 "ready"フラグが別のプロセッサーに表示されるようにすることができます。2番目のプロセッサが不完全なデータセットを読み込むので、これは実際には物事を混乱させます。

非アトミック操作でデータを書き込んだ後、フラグにアトミックデータ型を使用できます。 C++ 11では、適切なメモリバリアと同期が生成され、フラグセットを参照するスレッドにデータが確実に表示されます。 2つのスレッドがデータを書き込むための未定義の動作ですが、実際には問題ありません。

また、計算を実行する各スレッドによって割り当てられたヒープメモリのブロックにデータを格納し、アトミックポインタ変数を設定するために比較およびスワップ操作を使用します。比較とスワップに失敗した場合、別のスレッドが最初にそこに着いたので、データを解放します。

+0

フラグ変数の問題とメモリバリアの必要性を指摘するための最良の答えとして選択されています。 –

+0

@Anthony、C++のデータ競争の技術的な意味と競合状態の一般的な概念と+1。 C++は競合するデータとUBが発生したときのみ指定します。したがって、共有変数がC++ 11のアトミック型であれば、タグ付きの/ memory_order_relaxedの順序付けが行われていても、データ競合が発生しないことが保証されています。この規格では一般的な競合条件の概念について言及しておらず、UBが存在するとは言いません。正しいセマンティクスを保証するために、正しい同期プリミティブを追加することは、プログラマに完全に依存しています。同意しますか? –

+0

@Anthony、共有データが緩やかな順序付けを伴う原子型のものであっても、C++ 11にはまだデータ競合があると信じている人もいます。私はこれらの概念(あなたの本だけでなく言語でも正しく定義されています)を説明しようとするのは苦労しました。 [この投稿](https://groups.google.com/forum/#!topic/comp.lang.c++.matedated/1SN85LRbouw)をご覧になり、ご意見をお聞かせください。ありがとう。 –

1

最終的には、あなたのデータ構造によって決まります。

"移植不可能な"領域では、compare and swapを調べて、ほとんどのプロセッサがポインタサイズのエンティティで行うことができます。これにアクセスするには、インラインアセンブリ(x86ではlock cmpxchg命令)を使用するか、おそらくGCC同期拡張を使用します。初期化されていない値を見ると、各スレッドは熱心に初期化し、比較とスワップを発行して値を設定しようとします。比較とスワップが失敗した場合は、別のスレッドがそれに勝つことを意味します。

最終的にはその操作の使用は、多くの場合、あなたは避けるように見ているかもしれない、しかし、スピンロックを実装するのと同等されて終わる...

+0

データ構造の一部は8バイトの(32ビット)ARM上の倍数になるので、 'ポインタサイズのエンティティ'よりも大きくなります。だから、これは選択肢ではありません。 –

+0

@GabrielSchreiber - それはオプションではありません。たとえば、構造体自体の代わりに構造体へのポインタを初期化することができます。あるいは、初期化の前後でロックとして機能する「コンパニオン」という言葉を前もって初期化していたかどうかを判断することができます。しかし、一般的には、一度に複数の単語を書くことに頼っているのであれば、物事を「原子」と呼ぶことに注意することをお勧めします。 – asveikau

+0

@GabrielSchreiber - 読み込み - 変更 - 書き込み操作をアトミックにしたい場合は、compare-and-swap(またはプラットフォーム固有の同等物; RISCバージョンである「load-link/store-conditional」)多くの唯一の方法を行く。 – asveikau

1

あなたは、異なるスレッドからPOD変数への書き込みに対して保護する必要はありません。値が同じである場合あなたがポインタを持っているなら、間違いなく連動交換を行うべきです。

更新:明確にするために、キャッシュと最適化はすべてのスレッドにまったく同じ値を書いているので、悪影響はありません。同じ理由から、変数volatileを作成する必要はありません。潜在的に問題となるのは、変数がマシンのワードサイズに揃っていない場合だけです。詳細は、https://stackoverflow.com/a/54242/677131を参照してください。デフォルトでは、変数は自動的に揃えられますが、アライメントを明示的に変更できます。

この問題を完全に回避する代替方法があります。変数は同じ値を持つため、並行実行が開始される前に事前計算を行うか、各スレッドに独自のコピーを持たせます。後者は、NUMAマシンでより良いパフォーマンスを提供するという利点があります。

1

私は複数のスレッドから同じ変数への書き込みロックは、通常はそれを行うの正しい方法ですが...

を使用すると、データがプロセッサ・ワードよりも大きい場合でも危険なことができないと言って開始する必要がありますサイズ。少なくとも1つのスレッドが値の書き込みを終了するため、変数が破損する過渡的な状態はありません。他のスレッドは、同じ値に絞って変更しません。

したがって、計算結果がどのスレッドであっても常に同じになることが保証されている場合、複数のスレッドでそれを行うことに危険はありません。計算を行う前にフラグをチェックしてください( "既に計算されていますか?")。複数のスレッドは値の計算コードを入力しますが、いったん完了すると、他のスレッドはそれ以上行いません。明らかに同じことをn回行うことは時間の無駄です。ここでの質問は、ロックを使用すると、いつでも、または逆にあなたを救うでしょうか?パフォーマンステストだけで答えが得られます。ロックを使用しない理由が他にない限り。

関連する問題