整数segfaultへの同時読み書き(C++ 03標準では禁止されていませんが、私はPOSIXがそうしているかどうかわかりませんが)の実装についてはわかりません。ベクトルがpImplを使用していてベクトルオブジェクト自体にサイズを格納していない場合、別のスレッドで解放されたpImplオブジェクトからサイズを読み取ろうとすると問題が発生する可能性があります。たとえば、私のマシン上のGCCは、pImplを使用しています(サイズを直接保存しません。begin()とend()の違いとして計算されるため、変更中の競合条件には明らかな機会があります)。
でも、クラッシュしないとしても、無意味であるか間違った答えを与える可能性があります。あなたがロックしない場合は、読み値は、例えば次のようになります。
を使用すると、1つの値の最上位半分と他の最下位の半分を得る意味、非アトミック読み。実際には、size_tがアーキテクチャーの自然なワードサイズになる理由があるので、size_tの読み込みはおそらくほとんどの実装では基本的ではありません。しかし、それが起こった場合、これは、「前」ではなく「後で」が0でないときに値を0として読み取ることができる。例えば、遷移0x00FF→0x0100を考える。 「後」の下半分と「前」の上半分を取得すると、0が読み取られます。
任意に失効しました。ロック(またはその他のメモリバリア)がなければ、キャッシュから値を得ることができます。そのキャッシュが他のCPU /コアと共有されておらず、アーキテクチャにいわゆる「コヒーレントキャッシュ」がない場合は、別のスレッドを実行している別のCPUやコアが6週間前にサイズを変更する可能性があります。新しい値を参照してください。さらに、異なるアドレスは異なる量のメモリ残量になる可能性があります。メモリ障壁がなければ、別のスレッドがpush_backを実行した場合、ベクトルの最後に新しい値が表示されますが、サイズが大きくなることはありません。
共通のアーキテクチャでは、多くの問題が隠されています。たとえば、x86にはコヒーレントキャッシュがあり、volatile
オブジェクトにアクセスすると、MSVCは完全なメモリバリアを保証します。 ARMはコヒーレントキャッシュを保証しませんが、実際にはマルチコアARMはそれほど一般的ではないため、通常はダブルチェックロックも通常動作します。
これらの保証はいくつかの困難を解決し、いくつかの最適化を可能にします。なぜなら、それらが最初に作られた理由ですが、それらは普遍的なものではありません。明らかに、C++標準を超えていくつかの前提を設定することなく、マルチスレッドコードを書くことはできませんが、ベンダー固有の保証が多いほど、コードの可搬性が低下します。特定の実装を参照する以外に、あなたの質問に答えることはできません。
移植性のあるコードを書いているのであれば、実際にはすべてのメモリの読み取りと書き込みを、スレッドの専用メモリキャッシュにあると考えるべきです。メモリ・バリア(ロックを含む)は、他のスレッドからの書き込みおよび/または「インポート」書き込みを「公開」する手段です。バージョン管理システム(または何かのローカルコピーの他の好きな他の例)との類似点は明らかですが、あなたがそれらを依頼しなくても、いつでも公開/インポートされるという違いがあります。私が見ていない間に業界が最終的にトランザクションメモリを実装しない限り、もちろんマージや競合の検出はありません;-)
私の考えでは、マルチスレッドコードはまず共有メモリを避け、プロファイルを作成してから、競合やロックフリーのアルゴリズムを心配してください。最終段階に到達したら、特定のコンパイラとアーキテクチャのための十分にテストされた原則とパターンを研究し、それに従う必要があります。 C++ 0xは、あなたが信頼できるものをいくつか標準化することによっていくらか助けてくれるでしょうし、Herb Sutterの「Effective Concurrency」シリーズの中には、これらの保証をどのように利用するかについて詳しく述べています。記事の1つに、ロックフリーのマルチライターのシングルリーダーキューが実装されています。このキューは、目的に応じて使用できます。
素敵な答えです。バージョン管理の類推は、単に適切であるだけでなく、有用でもあります。 – quark