12

単純な 2 スレッド通信の例であっても、これを C11 アトミックおよび memory_fence スタイルで表現して適切なメモリ順序を取得するのは困難です。

共有データ:

volatile int flag, bucket;

プロデューサー スレッド:

while (true) {
   int value = producer_work();
   while (atomic_load_explicit(&flag, memory_order_acquire))
      ; // busy wait
   bucket = value;
   atomic_store_explicit(&flag, 1, memory_order_release);
}

消費者スレッド:

while (true) {
   while (!atomic_load_explicit(&flag, memory_order_acquire))
      ; // busy wait
   int data = bucket;
   atomic_thread_fence(/* memory_order ??? */);
   atomic_store_explicit(&flag, 0, memory_order_release);
   consumer_work(data);
}

私が理解している限り、上記のコードは、store-in-bucket -> flag-store -> flag-load -> load-from-bucket を適切に順序付けます。ただし、バケットからのロードと新しいデータでのバケットの再書き込みの間に競合状態が残っていると思います。atomic_thread_fence()バケットの読み取りに続く順序を強制するには、バケットの読み取りと次のatomic_storeの間に明示的なものが必要になると思います。残念ながらmemory_ordermemory_order_seq_cst.

本当に汚い解決策は、消費者スレッドでダミー値を再割り当てbucketすることです。これは、消費者の読み取り専用の概念と矛盾します。

古い C99/GCC の世界では、__sync_synchronize()十分に強力であると信じている従来型を使用できました。

このいわゆる反依存性を同期させるための、より優れた C11 スタイルのソリューションは何でしょうか?

(もちろん、そのような低レベルのコーディングを避け、利用可能な高レベルの構造を使用する必要があることは承知していますが、理解したいと思います...)

4

3 に答える 3

3

バケット読み取りに続く順序を強制するには、バケット読み取りと次のatomic_storeの間に明示的なatomic_thread_fence()が必要になると思います。

呼び出しが必要だとは思いませんatomic_thread_fence()。フラグの更新にはリリース セマンティクスがあり、先行するロードまたはストア操作がそれをまたがって並べ替えられるのを防ぎます。Herb Sutter による正式な定義を参照してください。

write-release は、プログラム順で先行する同じスレッドによるすべての読み取りと書き込みの後に実行されます。

これにより、コンパイラが保存する場所に関係なくbucket、更新後に の読み取りが再順序付けされるのを防ぐ必要があります。flagdata

それは別の答えについてのあなたのコメントに私をもたらします:

これvolatileにより、ld/st 操作が生成され、その後フェンスで順序付けできるようになります。ただし、データは揮発性ではなく、ローカル変数です。コンパイラーは、ストア操作を回避して、おそらくそれをレジスターに入れます。これにより、バケットからのロードは、後続のフラグのリセットで注文されます。

書き込みリリースをbucket超えて読み取りを並べ替えることができない場合、それは問題ではないように思われるため、必要ではありません (おそらく、それを持っていても害はありません)。また、ほとんどの関数呼び出し (この場合は) がコンパイル時のメモリ バリアとして機能するため、これも不要です。インライン化されていない関数呼び出しが同じ変数を変更する可能性があるため、コンパイラはグローバル変数の読み取りを並べ替えません。flagvolatileatomic_store_explicit(&flag)

pauseまた、互換性のあるアーキテクチャをターゲットにする場合は、指示を使用してビジー待機を改善できるという @MaximYegorushkin にも同意します。GCC と ICC はどちらも_mm_pause(void)組み込み関数を持っているようです (おそらく と同等__asm__ ("pause;")です)。

于 2013-10-31T13:45:09.273 に答える
1

@MikeStrobel のコメントに同意します。

atomic_thread_fence()クリティカル セクションは取得で始まり、リリース セマンティクスで終わるため、ここでは必要ありません。したがって、クリティカル セクション内の読み取りは、取得前に並べ替えることはできず、リリース後に書き込みを行うことはできません。volatileそして、これがここでも不要な理由です。

さらに、代わりに (pthread) スピンロックが使用されない理由がわかりません。pausespinlock は同様のビジー スピンを行いますが、命令も使用します。

組み込みの一時停止は、プロセッサが動的実行 (特に順不同の実行) を実装するスピン待機ループで使用されます。スピン待機ループでは、一時停止組み込みにより、コードがロックの解放を検出する速度が向上し、特にパフォーマンスが大幅に向上します。次の命令の実行は、実装固有の時間だけ遅延されます。PAUSE 命令はアーキテクチャの状態を変更しません。動的スケジューリングの場合、PAUSE 命令はスピンループから出るペナルティを減らします。

于 2013-10-30T20:30:09.967 に答える