残りのアトミックは、std::atomic_flag とは対照的に、クラス テンプレート std::atomic の部分的または完全な特殊化です。 std::atomic
std::atomic
std::atomic
まず、条件変数を見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | // conditionVariable.cpp #include <condition_variable> #include <iostream> #include <thread> #include <vector> std::vector<int> mySharedWork; std::mutex mutex_; std::condition_variable condVar; bool dataReady; void waitingForWork(){ std::cout << "Waiting " << std::endl; std::unique_lock<std::mutex> lck(mutex_); condVar.wait(lck,[]{return dataReady;}); mySharedWork[1]= 2; std::cout << "Work done " << std::endl; } void setDataReady(){ mySharedWork={1,0,3}; { std::lock_guard<std::mutex> lck(mutex_); dataReady=true; } std::cout << "Data prepared" << std::endl; condVar.notify_one(); } int main(){ std::cout << std::endl; std::thread t1(waitingForWork); std::thread t2(setDataReady); t1.join(); t2.join(); for (auto v: mySharedWork){ std::cout << v << " "; } std::cout << "\n\n"; } |
そして今、アトミックブール値を持つペンダントです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | // atomicCondition.cpp #include <atomic> #include <chrono> #include <iostream> #include <thread> #include <vector> std::vector<int> mySharedWork; std::atomic<bool> dataReady(false); void waitingForWork(){ std::cout << "Waiting " << std::endl; while ( !dataReady.load() ){ // (3) std::this_thread::sleep_for(std::chrono::milliseconds(5)); } mySharedWork[1]= 2; // (4) std::cout << "Work done " << std::endl; } void setDataReady(){ mySharedWork={1,0,3}; // (1) dataReady= true; // (2) std::cout << "Data prepared" << std::endl; } int main(){ std::cout << std::endl; std::thread t1(waitingForWork); std::thread t2(setDataReady); t1.join(); t2.join(); for (auto v: mySharedWork){ std::cout << v << " "; } std::cout << "\n\n"; } |
17行目が14行目の後に実行されることを保証するものは何ですか?または、より一般的に言えば、スレッド t2 が mySharedWork={1,0,3} (22 行目) を実行した後、スレッド t1 が mySharedWork[1]=2 (17 行目) を実行します。よりフォーマルになりました。
- Line22 (1) 前に起こる 23行目 (2)
- 14 行目 (3) 前に起こる 17 行目 (4)
- 23 行目 (2) 同期 14 行目 (3)
- なぜなら 前に起こる mySharedWork={1,0,3} (1) は推移的です。 前発生 mySharedWork[1]=2 (4)
一点だけ明示したい。条件変数 condVar またはアトミック dataReady により、共有変数 mySharedWork へのアクセスが同期されます。これは、mySharedWork がロックまたはアトミックによって保護されていないにもかかわらず保持されます。
どちらのプログラムも、mySharedWork に対して同じ結果を生成します。
プッシュ対プルの原則
明らかに、私は少しだまされました。条件変数とアトミック ブール値を使用したスレッドの同期には、1 つの違いがあります。条件変数は、待機中のスレッド (condVar.notify()) に、作業を続行する必要があることを通知します。しかし、アトミック ブール値を持つ待機中のスレッドは、送信者がその作業を完了したかどうかを確認します (dataRead=true)。
条件変数は、待機中のスレッドに通知します (プッシュ原則)。アトミック ブール値は繰り返し値を要求します (プル原理)。
compare_exchange_strong と compare_exchange_weak
std::atomic
atomicValue.compare_exchange_strong(expected, desired) の呼び出しは、次の戦略に従います。 atomicValue と expected のアトミック比較が true を返す場合、atomicValue の値は同じアトミック操作で desired に設定されます。比較で false が返された場合、expected は atomicValue に設定されます。操作compare_exchange_strongが強いと呼ばれる理由は簡単です。メソッドcompare_exchange_weakがあります。この脆弱なバージョンは、誤って失敗する可能性があります。つまり、*atomicValue ==が成り立つことが期待されますが、弱いバリアントは false を返します。そのため、ループ内で条件を確認する必要があります:while ( !atomicValue.compare_exchange_weak(expected, desired) )。弱いフォームの理由はパフォーマンスです。一部のプラットフォームでは、weak は strong バリアントよりも高速です。
次は?
次の投稿は、クラス テンプレート std::atomic についてです。そこで、積分とポインタのさまざまな特殊化について書きます。これらは、アトミック ブール値 std::atomic