1 つのスレッドがゼロ初期化された配列の奇数ビットをすべて設定し、別のスレッドが偶数ビットをすべて設定した場合、メモリには何が格納されるでしょうか。
短い答え #
更新がアトミックでない限り、最終値は決定論的ではありません。C と C++ では、同じ ストレージへの同期されていない並行書き込みはデータ競合を形成するため、プログラムの 動作は未定義です。
キャッシュコヒーレンスはコア間でキャッシュラインの一貫性を保ちますが、リード・モディ ファイ・ライトのシーケンスを単一の不可分な操作には変えません。2 つのスレッドが同じ バイトまたはワード内の異なるビットを通常のストアで更新すると、両方のスレッドが同じ 古い値を読み、それぞれローカルで変更してから、古いコピーを書き戻す可能性があります。 その結果、一方の更新が他方を上書きすることがあります。
キャッシュラインでは競合を解決できない理由 #
コヒーレンスはキャッシュライン粒度で動作します。これはパフォーマンスに影響します。 同じラインにアクセスする 2 つのスレッドは、所有権を何度も受け渡すことになるためです。
しかし、正しさには影響しません。競合はキャッシュプロトコルより上位で発生するためです。
- スレッド A が共有ワードを読み取る。
- スレッド B が同じワードを読み取る。
- A が自身のビットを設定して書き戻す。
- B が古い値に基づいて自身のビットを設定して書き戻す。
最後の書き込みが触れたバイトについては最後の書き込みが勝つため、先行するビット更新が 失われる可能性があります。
結果が安定する場合 #
各更新がアトミックなビット単位の操作であれば、答えは決定論的になります。
atomic_fetch_or_explicit(&word, mask, memory_order_relaxed);この場合、ハードウェアと言語ランタイムが共有ワードの変更を直列化するため、最終値は すべての更新のビット単位 OR になります。
各スレッドが互いに重ならないバイト、ワード、またはキャッシュラインに書き込む場合にも 同じ結果になります。重要なのは「奇数ビットと偶数ビット」の区別ではありません。2 つの 書き込みが、マシンがアトミックに更新するストレージ単位で重なる可能性があるかどうかが 重要です。
実践的なルール #
- パックされたビットセットには、アトミックなリード・モディファイ・ライト操作を使用する。
- 高頻度でアクセスされる並行カウンターまたはフラグでは、スレッドが同じキャッシュラインを共有しないようデータを分割する。
- 単純なパフォーマンス作業では、論理的にデータが分離されていても、フォルスシェアリングがスループットを低下させることを忘れない。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)