質問: 0; i < 1000; ++i) { thws[i].join(); thrs[i].join(); } std::cout << x << "," << y << std::endl; return 0; } ``` `x`は原子的`int`ですが`y`は普通の`int`です。しかし、インクリメントの処理について、mutexによりどちらも排他制御されているため、期待通りに最終的に1000になります(もし、mutexがなければ、`y`は1000で無いときがあります)。 では、読み込み側はどうでしょうか?1は安全でしょう。`x++`の最中に`x`が読み込まれて壊れてしまうことはありません。では2はどうなのでしょうか?`y++`の最中に`y`が読み込まれた場合、おかしな値になることはあるのでしょうか?また、後置インクリメント以外、例えば、前置インクリメントや代入、`+=`などの場合は、うまくいかないとかあるのでしょうか?そして、それらのことは`int`以外の、`bool`や`long long`等の他の整数型についても言えることなのでしょうか?(とりあえず、上のコードを手元で試す限り、`y`がおかしな値を返すことはないようです。) もし、問題が無ければ、atomicを外して僅かでも処理を速くしたいと考えています(排他制御自体は別の理由で外せないことが前提です)。変更が全て排他制御されていれば、atomicは外しても大丈夫なのかどうかを教えてください。
回答: > > 原子的だと思われる型について、変更がある場所を全て排他制御されている場合でも、atomicを使う必要はあるのでしょうか? > > > C++言語仕様の観点からは「atomicは必須です」。atomicでない変数に関して、同時にRead/Writeが行われると **未定義動作(Undefined Behavior)** です。 > > 変更が全て排他制御されていれば、atomicは外しても大丈夫なのかどうかを教えてください。 > > > 上記理由により「NG」です。**変更と読込**全てを排他制御するか、素直にatomic変数を利用してください。 > > では2はどうなのでしょうか?`y++`の最中に`y`が読み込まれた場合、おかしな値になることはあるのでしょうか? > > > C++言語仕様としては何の保証もありませんから、「おかしな値になる」ことも含めて何が起きても不思議ではありません。 > > (とりあえず、上のコードを手元で試す限り、yがおかしな値を返すことはないようです。) > > > 現実のC++コンパイラとプロセッサアーキテクチャの下では、大抵のケースでプログラマの期待通り振舞うと予想されます。しかし、繰り返しになりますが仕様上は一切の保証がなくなりますので、理論上は壊れているプログラムです。 (「目の前のコードが動いているから良いのだ」という態度はそれはそれでアリです。あくまで自己責任で。) > > atomicを外して僅かでも処理を速くしたいと考えています(排他制御自体は別の理由で外せないことが前提です) > > > 正直言っておすすめはできませんが、atomic変数の操作にmemory orderを指定することで処理の高速化を図れる可能性があります。 ただし、以下の条件付きです: * C++言語のメモリモデルを理解していること。少なくとも、relaxed/release-acquire/sequential consistencyメモリアクセスの違いを正しく認識していないと、プログラムに超難解な潜在バグを埋め込むだけです。 * 対象アーキテクチャのハードウェアメモリモデルを十分に理解していること。ARMアーキテクチャなど弱いメモリモデルでは大きな効果を得られる可能性がありますが、x86アーキテクチャなど強いメモリモデルでは期待するほどの効果は得られないでしょう。 また、対象プログラムにおいてatomicアクセスが真の問題になるほど**重い**かは十分考慮してください。(個人的には)そもそも並列化設計が適切でないことがほとんどと思います。その変数は本当に並行更新する必要があるのでしょうか?Map-Reduce方式のような更新はできないのでしょうか? 下記記事もご参考に: * メモリモデル?なにそれ?おいしいの? * C#でマルチスレッドのベストプラクティスって何かある?(What are the best practices with multithreading in C#?)