主にアトミック処理に関する質問です。
いくつか不明な点があります。
アトミック処理とミューテックスの違いについて確認しておきます。
mutex と atomic の違いは何か、どちらが良いか
アトミック操作
アトミック操作の長所は、ロックに比べて比較的処理が速く、デッドロックやコンボイなどを回避できる点にあります。 短所は、限られた操作しか行うことができず、より複雑な操作を効率良く総合的に扱うには不十分なことです。
次に、ロックフリーについて・・・
C++ストラウストラップ氏の本の中では以下のようにあります。
ロックフリープログラミングは明示的なロックを使わずに並行プログラムを開発するためのの一連の技法だ。
明示的なロックの代わりに使うのが、小規模オブジェクトに対するデータ競合を防ぐための(ハードウェアが直接サポートする)基礎的な演算である。データ競合が発生しない基礎的な演算は、一般的にアトミック処理と呼ばれる。
・・・
ロックフリー技法を用いると、ロック技法を用いた場合よりも大幅に速度が向上することもある。
標準アトミック型と処理は、従来方式のロックフリーなコードに代わる、可搬性を持った代替手法を提供する。
[疑問1]
アトミック処理とは、ミューテックスと同じように排他的処理を提供するが、ミューテックスとは対照的にロックを使用しない。
つまり、アトミック処理 = ロックフリー 完全に同値なものだと解釈してよろしいのでしょうか?
アトミックとミューテックスを併用したプログラミングスタイルというものも存在するのでしょうか?
また、アトミック処理以外にもロックフリーを実現することは可能なのでしょうか??
[疑問2]
アトミック処理は、ロックを使用しないのに、どうやって排他的制御を実現しているのでしょうか??
自分の予想なのですが・・・これってもしかしてCPUにアトミック専用命令みたいなものが用意されている?・・・のでしょうか? EX : add命令 -> atomic.add命令 みたいな感じですかね。
ここからvolatile
が出てきます。
C++ストラウストラップ氏の本から以下抜粋です。
アトミック処理は、メモリ状態が、指定したメモリオーダの要求通りとなることを保証する。
(メモリオーダーとは)コンパイラやオプティマイザはプログラムの実行速度向上を目的に、命令の順序を並べ替えることがある。
・・・
もっとも単純なメモリオーダーは、逐次一貫と呼ばれるものだ。逐次一貫メモリモデルでは、すべてのスレッドは、実行されたすべての処理の結果が同じ順序として見える。その順序は、単一スレッドで命令を逐次実行した場合の順序である。
スレッドが命令の実行順序を変更することはあり得るが、他のスレッドが変数を参照する時点では、それまで実行された処理、及び(その結果である)メモリロケーションの値が的確に定義された状態であることが保証されて、全スレッドから同じものが見える。メモリロケーションに一貫した観点を強制した上で値を”見る”ことをアトミック処理と呼ぶ。
ふぅーーーー。ちょっと長いですね。
要は、命令順序は変更されることは、あっても変数内の値は、的確に定義された状態であることが保証されているということですよね。
では、ここで別の書籍のサンプルコードを見てみます。
C++
1volatile std::atomic_flag shared_lock; 2 3void thread_function(int id){ 4 while(shared_lock.test_and_set()) 5 sleep(1); 6 shared_data = id; 7 std::cout << "Thread " << id << " set shared value to " << shared_data << std::endl; 8 usleep(id * 100); 9 std::cout << "Thread " << id << " has shared value as " << shared_data << std::endl; 10 shared_lock.clear(); 11}
ん??
volatile
が出てきています。
volatileが必要な場面を見つけ出す
[疑問3]
このvolatileはどのような効果を持っているのでしょうか?
命令順序は変更されることは、あっても変数内の値は、的確に定義された状態であることはアトミックによって保証されているはずですよね。
これって、volatileの効果とかぶっていませんか??
volatile変数は「コンパイラからは検知できない任意タイミングで更新される可能性のある変数」
スレッドには当てはまらない。はずです。
となると、このvolatileは命令の実行順序と関係がありそうですね。
アトミックはvolatileの性質を含んでいるのでしょうか??
[疑問4]
アトミックとは別にvolatileによく似ているのが、メモリフェンス(メモリバリア)の存在です。
CPUやコンパイラは、メモリフェンスを越えて読み込みと書き込みの操作の順序を入れ替えることができない。
volatile宣言がされている場合、コンパイラはそのデータが置かれたメモリに対する読み書きの順序を変更できない。しかし、これらの読み書きと、他のメモリに対する読み書きとの順序を変更してもよい。
ん〜〜??
なんか言っている事が同じ気がするんですが・・・
volatileとメモリバリアの違いがハッキリしません。
アトミック処理は、メモリ状態が指定したメモリオーダーの要求通りとなる事を保証する。
デフォルトのメモリオーダーは、memory_order_seq_cst
である。
例
C++
1int x = 0, y = 0, r1 = 0, r2 = 0; 2 3// スレッド1 4x = 1; 5r1 = y; 6 7// スレッド2 8y = 1; 9r2 = x;
プログラム終了時、r1とr2の値はどちらも0になる可能性がある。この状況が発生するのは、スレッド2が1を代入する前にスレッド1がyを読み込んでしまう場合、もしくはスレッド2はyに1を代入したが、値がキャッシュされ、スレッド1がyを読む際に値が反映されていない場合がある。
メモリバリアはこれらのデータ競合を緩和する低レベルな方法である。
次のコードはメモリフェンスを追加している。
C++
1int x = 0, y = 0, r1 = 0, r2 = 0; 2 3//スレッド1 4x = 1; 5atomic_thread_fence(memory_order_seq_cst); 6r1 = y; 7 8//スレッド2 9y = 1; 10atomic_thread_fence(memory_order_seq_cst); 11r2 = x;
これ、volatileではダメなんでしょうか?
[疑問5]
疑問2と似ているのですが・・・このメモリバリアと呼ばれるものはどうやって実現しているのでしょうか?
これも、CPUにメモリバリア専用命令みたいなのが用意されているのでしょうか?
メモリバリアの atomic_thread_fence
の名前でatomicが付いているので、そうなのかなぁ〜〜という推測です。
疑問3と4は、アトミックとvolatileとメモリバリアの違いについての疑問です。
わかる方お願いします。
環境は、g++ Linux です。
[追記]
疑問3については、volatileが必要な場面を見つけ出すの中でChironianさんが触れていることに非常に近いですね。
なるほど。そう言えば、関数呼び出しを挟めば最適化が抑止されたことがありました。そのように規定されているのですね!(lock()が同期用のロックかどうかコンパイラは把握できない筈ですし)
アトミック変数はどうでしょう? 必要に応じて暗黙的にvolatileになるのでしょうか?
アトミック変数がvolatileの性質を含んでいるような気がしますが・・・
質問中のコードにはなぜvolatileが登場しているんでしょうかね・・・

回答6件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/12/25 03:20
2018/12/25 03:46
2018/12/25 04:00
2018/12/25 04:14
2018/12/25 05:29
2018/12/25 05:34 編集
2018/12/25 05:56 編集
2018/12/25 06:17
2018/12/25 06:25
2018/12/25 06:34 編集
2018/12/25 06:49
2018/12/25 06:56
2018/12/25 07:11 編集
2018/12/25 10:10
2018/12/25 11:11
2018/12/25 11:25
2018/12/25 11:37
2018/12/25 11:50
2018/12/25 11:51