web-dev-qa-db-ja.com

どのstd :: sync :: atomic :: Orderingを使用しますか?

_std::sync::atomic::AtomicBool_ のすべてのメソッドは、これまで使用したことのないメモリオーダリング(Relaxed、Release、Acquire、AcqRel、およびSeqCst)を使用します。これらの値はどのような状況で使用する必要がありますか?ドキュメントでは、私が本当に理解していない紛らわしい「ロード」と「ストア」の用語を使用しています。例えば:

プロデューサースレッドは、 Mutex によって保持されている状態を変更してから、 AtomicBool :: compare_and_swap(false, true, ordering)を呼び出します(合体するため)無効化)、およびスワップされた場合は、「無効化」メッセージを並行キューに送信します(例: mpsc またはwinapi PostMessage)。コンシューマースレッドはAtomicBoolをリセットし、キューから読み取り、ミューテックスによって保持されている状態を読み取ります。プロデューサーは、ミューテックスが前に付いているため、リラックスした順序を使用できますか、それともリリースを使用する必要がありますか?コンシューマーはstore(false, Relaxed)を使用できますか、それともミューテックスから変更を受け取るためにcompare_and_swap(true, false, Acquire)を使用する必要がありますか?

プロデューサーとコンシューマーがRefCellの代わりに Mutex を共有する場合はどうなりますか?

35
yonran

私はこれの専門家ではなく、とても複雑なので、遠慮なく私の投稿を批評してください。 mdh.heydariが指摘しているように、cppreference.comには 注文に関するはるかに優れたドキュメント がRust(C++のAPIはほぼ同じ)よりも優れています).


あなたの質問のために

プロデューサーでは「リリース」注文を使用し、コンシューマーでは「取得」注文を使用する必要があります。これにより、AtomicBoolがtrueに設定される前にデータの変更が確実に発生します。

キューが非同期の場合、プロデューサーはAtomicBoolの設定とキューへの何かの配置の間に中断される可能性があるため、コンシューマーはループでキューからの読み取りを試行し続ける必要があります。

クライアントが実行される前にプロデューサーコードが複数回実行される可能性がある場合、クライアントがデータを読み取っている間にデータを変更する可能性があるため、RefCellを使用することはできません。それ以外の場合は問題ありません。

このパターンを実装するためのより良い、より簡単な方法は他にもありますが、例として挙げただけだと思います。


注文とは何ですか?

異なる順序は、アトミック操作が発生したときに別のスレッドが発生することを確認することと関係があります。コンパイラとCPUは通常、コードを最適化するために命令を並べ替えることができます。順序は、命令を並べ替えることができる量に影響します。

常にSeqCstを使用できます。これにより、基本的に、他の命令と比較してどこに配置しても、その命令が発生したと全員に表示されますが、場合によっては、制限の少ない順序を指定すると、LLVMとCPUの方が優れていることがありますコードを最適化します。

これらの順序は、(命令に適用するのではなく)メモリ位置に適用するものと考える必要があります。

注文タイプ

リラックスした注文

アトミックであるメモリ位置への変更以外に制約はありません(したがって、完全に発生するか、まったく発生しません)。個々のスレッドによって取得/設定された値がアトミックである限り重要でない場合、これはカウンターのようなものには問題ありません。

注文の取得

この制約は、「取得」が適用された後にコードで発生する変数の読み取りは、その前に発生するように並べ替えることができないことを示しています。したがって、コードで共有メモリの場所を読み取り、値Xを取得するとします。この値は、時間Tにそのメモリの場所に格納され、「取得」制約を適用します。制約を適用した後に読み取ったメモリ位置は、T以降の時点の値になります。

これはおそらくほとんどの人が直感的に起こると予想することですが、CPUとオプティマイザーは結果を変更しない限り命令を並べ替えることができるため、保証されません。

「acquire」を有効にするには、「release」とペアにする必要があります。そうしないと、他のスレッドがTに発生するはずの書き込み命令を並べ替えなかったという保証がないためです。以前の時間。

取得-探しているフラグ値を読み取ることは、リリース前の書き込みによって実際に変更された古い値が他の場所に表示されないことを意味します-フラグに保存します。

リリース注文

この制約は、「リリース」が適用される前にコードで発生した変数の書き込みは、その後に発生するように並べ替えることができないことを示しています。したがって、コードでいくつかの共有メモリ位置に書き込み、時間Tにメモリ位置tを設定してから、「解放」制約を適用するとします。 「リリース」が適用される前にコードに表示される書き込みは、その前に発生したことが保証されます。

繰り返しますが、これはほとんどの人が直感的に起こることを期待するものですが、制約なしでは保証されません。

Xを読み取ろうとしている他のスレッドが「取得」を使用しない場合、他の変数値の変更に関して新しい値を確認することは保証されません。したがって、新しい値を取得することはできますが、他の共有変数の新しい値は表示されない可能性があります。また、テストは難しいことにも注意してください。一部のハードウェアは、実際には安全でないコードでの並べ替えを表示しないため、問題が検出されない可能性があります。

Jeff Preshingが取得と解放のセマンティクスについての素晴らしい説明を書いています なので、これが明確でない場合は読んでください。

AcqRelの注文

これは、AcquireReleaseの両方の順序付けを行います(つまり、両方の制限が適用されます)。これがいつ必要かはわかりません。3つ以上のスレッドがある状況で、ReleaseAcquire、および両方を実行する場合に役立つ可能性がありますが、実際にはわかりません。 。

SeqCstの注文

これは最も制限が厳しく、したがって最も遅いオプションです。これにより、メモリアクセスがすべてのスレッドに対して1つの同じ順序で発生しているように見えます。これには、アトミック変数(StoreLoadを含む完全なメモリバリア)へのすべての書き込みでx86にMFENCE命令が必要ですが、弱い順序では必要ありません。 (SeqCstのロードでは、 このC++コンパイラの出力 でわかるように、x86でバリアは必要ありません。)

アトミックインクリメントやコンペアアンドスワップなどのリードモディファイライトアクセスは、すでにフルメモリバリアであるlocked命令を使用してx86で実行されます。 x86以外のターゲットで効率的なコードにコンパイルすることにまったく関心がある場合は、アトミックな読み取り-変更-書き込み操作の場合でも、可能な場合はSeqCstを回避するのが理にかなっています。 必要な場合もあります ただし。

アトミックセマンティクスがASMに変換される方法のその他の例については、 C++アトミック変数に関するこのより大きな単純な関数のセット を参照してください。これがRustの質問ですが、基本的にC++と同じAPIを使用することになっています。godboltはx86、ARM、ARM64、およびPowerPCをターゲットにできます。興味深いことに、ARM64には負荷取得(ldar)およびストアリリース(stlr)命令。したがって、必ずしも個別のバリア命令を使用する必要はありません。


ちなみに、x86 CPUはデフォルトで常に「強力な順序」になっています。つまり、少なくともAcqRelモードが設定されているかのように常に動作します。したがって、x86の場合、「順序付け」はLLVMのオプティマイザーの動作にのみ影響します。一方、ARMは弱く注文されています。 Relaxedはデフォルトで設定されており、コンパイラーが自由に並べ替えることができ、順序の弱いCPUで追加のバリア命令を必要としません。

14
Michael Younkin