C ++ 11のストリーム、ロック、および条件変数[パート2]

この記事のより完全な理解のために、スレッドとロックに主な注意が払われた最初の部分を読むことをお勧めします。

この記事では、条件変数を調べます...



条件変数



前述の同期メソッドに加えて、C ++ 11は、別のスレッドからの通知が受信されるか、 神話上の 偽のウェイクアップ (「false /ランダムウェイクアップ」)が発生するまで1つ以上のスレッドをブロックできる条件変数のサポートを提供します。

<condition_variable>



ヘッダーで使用できる条件変数の実装は2つあります。



条件変数の仕組みを説明します。



以下のコードは、条件変数を使用してスレッドを同期する例を示しています。一部のスレッドの操作中(「ワーカー」と呼びましょう)、キューに配置されているときにエラーが発生する場合があります。 「レジストラ」スレッドは、これらのエラーを処理(キューから取得)し、印刷します。 エラーが発生すると、「Workers」は「registrar」に信号を送ります。 レジストラは、条件付き変数信号を予期しています。 誤った覚醒を回避するために、ブール条件がチェックされるループで予期が発生します。

 #include <condition_variable> #include <iostream> #include <random> #include <thread> #include <mutex> #include <queue> std::mutex g_lockprint; std::mutex g_lockqueue; std::condition_variable g_queuecheck; std::queue<int> g_codes; bool g_done; bool g_notified; void workerFunc(int id, std::mt19937 &generator) { //   { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[worker " << id << "]\trunning..." << std::endl; } //   std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); //   int errorcode = id*100+1; { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl; } //    { std::unique_lock<std::mutex> locker(g_lockqueue); g_codes.push(errorcode); g_notified = true; g_queuecheck.notify_one(); } } void loggerFunc() { //   { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[logger]\trunning..." << std::endl; } //   ,      while(!g_done) { std::unique_lock<std::mutex> locker(g_lockqueue); while(!g_notified) //    g_queuecheck.wait(locker); //     ,   while(!g_codes.empty()) { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[logger]\tprocessing error: " << g_codes.front() << std::endl; g_codes.pop(); } g_notified = false; } } int main() { //   -  std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); //   std::thread loggerThread(loggerFunc); //   std::vector<std::thread> threads; for(int i = 0; i < 5; ++i) threads.push_back(std::thread(workerFunc, i+1, std::ref(generator))); for(auto &t: threads) t.join(); //        g_done = true; loggerthread.join(); return 0; }
      
      





このコードを実行すると、おおよそ次の結果が得られます(ワーカースレッドがランダムな時間間隔で動作する(またはスリープする)ため、結果は毎回異なります)。

 [logger] running... [worker 1] running... [worker 2] running... [worker 3] running... [worker 4] running... [worker 5] running... [worker 1] an error occurred: 101 [worker 2] an error occurred: 201 [logger] processing error: 101 [logger] processing error: 201 [worker 5] an error occurred: 501 [logger] processing error: 501 [worker 3] an error occurred: 301 [worker 4] an error occurred: 401 [logger] processing error: 301 [logger] processing error: 401
      
      





上記のwait



メソッドには、2つのオーバーロードがあります。



したがって、2番目のオーバーロードを使用すると、上記の例でブールフラグg_notified



使用を回避できます。

 void workerFunc(int id, std::mt19937 &generator) { //   { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[worker " << id << "]\trunning..." << std::endl; } //   std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); //   int errorcode = id*100+1; { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl; } //    { std::unique_lock<std::mutex> locker(g_lockqueue); g_codes.push(errorcode); g_queuecheck.notify_one(); } } void loggerFunc() { //   { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[logger]\trunning..." << std::endl; } //   ,      while(!g_done) { std::unique_lock<std::mutex> locker(g_lockqueue); g_queuecheck.wait(locker, [&](){return !g_codes.empty();}); //     ,   while(!g_codes.empty()) { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "[logger]\tprocessing error: " << g_codes.front() << std::endl; g_codes.pop(); } } }
      
      





オーバーロードされたwait()



メソッドに加えて、述語のオーバーロードが同じ2つの類似したメソッドがあります。



述語なしでこれらのメソッドをオーバーロードすると、タイムアウトが発生したのか、条件変数信号によるウェイクアップが発生したのか、それが偽のウェイクアップであるのかを示すcv_statusが返されます。



Stdは、 notify_all_at_thread_exit関数も提供します。 この関数は、すべてのthread_local



オブジェクトの破棄など、データの処理が完了したことを他のスレッドに通知するメカニズムを実装します。 join



以外のメカニズムでスレッドを待機すると、 thread_locals



がすでに使用されており、スレッドがウェイクアップした後、またはスレッドが既に完了した後にデストラクターが呼び出される場合、異常な動作が発生join



可能性があります( N3070およびN2880を参照してください。通常、呼び出しこの関数は、スレッドが存在し始める直前に実行する必要があります。次に、 notify_all_at_thread_exit



を条件変数とともに使用して2つのスレッドを同期する方法の例を示します。

 std::mutex g_lockprint; std::mutex g_lock; std::condition_variable g_signal; bool g_done; void workerFunc(std::mt19937 &generator) { { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "worker running..." << std::endl; } std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "worker finished..." << std::endl; } std::unique_lock<std::mutex> lock(g_lock); g_done = true; std::notify_all_at_thread_exit(g_signal, std::move(lock)); } int main() { std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); std::cout << "main running..." << std::endl; std::thread worker(workerFunc, std::ref(generator)); worker.detach(); std::cout << "main crunching..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); { std::unique_lock<std::mutex> locker(g_lockprint); std::cout << "main waiting for worker..." << std::endl; } std::unique_lock<std::mutex> lock(g_lock); while(!g_done) //    g_signal.wait(lock); std::cout << "main finished..." << std::endl; return 0; }
      
      





ワーカーがメインスレッドの前に作業を終了すると、結果は次のようになります。

 main running... worker running... main crunching... worker finished... main waiting for worker... main finished...
      
      





メインスレッドがワーカースレッドの前に作業を終了すると、結果は次のようになります。

 main running... worker running... main crunching... main waiting for worker... worker finished... main finished...
      
      







結論として



C ++ 11標準により、C ++開発者は、プラットフォームに依存しない標準的な方法でマルチスレッドコードを記述できます。 この記事は、stdからのストリームと同期メカニズムの概要です。 <thread>



ヘッダーは、スレッドを表す同じ名前(および多くの追加機能)を持つクラスを提供します。 <mutex>



ヘッダーは、スレッドへのアクセスを同期するためのいくつかのミューテックスと「ラッパー」の実装を提供します。 <condition_variable>



ヘッダーは、別のスレッドから通知を受け取るまで、または偽の目覚めの前に、1つ以上のスレッドをブロックできる条件変数の2つの実装を提供します。 より詳細な情報と問題の理解のために、もちろん、追加の文献を読むことをお勧めします:)



All Articles