C++条件変数std::condition_variableと虚偽の目覚まし

3438 ワード

std::condition_variable
std::condition_variableはC++11が新たに追加した複数のスレッド間の同期のためのメカニズムであり、ヘッダファイルはLinuxのpthread_に似ている.cond_t,条件が満たされている場合にのみスレッドがトリガーされ,満たされていない場合にはスレッドがブロックされ,std::unique_に合わせる必要がある.lockは、以下のように使用されます.
#include 
#include
#include              
#include    
#include    
#include   
#include  


std::mutex g_mtx; //      .
std::condition_variable g_cond; //       .
std::deque g_deque;

void consumeFunc(int id)
{
	while (true)
	{
		std::unique_lock <:mutex> lck(g_mtx);
		while (g_deque.empty())
		{		
			g_cond.wait(lck);
		}
		int aa = g_deque.back();
		std::cout << "thread " << id << "  " << aa << '
'; g_deque.pop_back(); lck.unlock(); } } void produceFunc() { while (true) { std::unique_lock <:mutex> lck(g_mtx); if (g_deque.empty()) { g_deque.push_back(1); g_cond.notify_all(); // . } lck.unlock(); std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread consumeThread[10]; for (int i = 0; i < 10; ++i) consumeThread[i] = std::thread(consumeFunc, i); std::thread produThread; produThread = std::thread(produceFunc); for (int i = 0; i < 10; ++i) { if (consumeThread[i].joinable()) { consumeThread[i].join(); } } if (produThread.joinable()) { produThread.join(); } return 0; }

生産者スレッドは絶えずデータを生産し、g_を入れる.Dequeでは、10個の消費者スレッドがg_から絶えずdequeでデータを取得して削除します.データがない場合、消費者スレッドはwait関数を呼び出して現在のスレッドをブロックし、生産者データがg_に入るとdequeの後にnotify_を呼び出すall関数は、待機しているすべてのスレッドを呼び出します.10個の消費者スレッドが共に競合するデータであるため、最終的には1個のスレッドだけがデータを得て、他の9個のスレッドが起動された後にg_が発見されるdequeは空のwait関数を呼び出し続けて自分をブロックし、現在のスレッドが起動しているのに必要なデータが得られないという虚偽の起動の概念をもたらした.
 
偽りが呼び覚ます
ネットの虚偽の呼び覚ましに対する解釈は主に2種類ある:1つは上で説明したnotify_all起動後に必要なデータが得られないシステムは、キューをブロックしているスレッドを何らかの理由で起動するシステムがあり、消費者スレッドも必要なデータが得られない(生産者スレッド起動ではないため).どちらの言い方も兼ねていて、間違いはありません.もっと正確な言い方は1つ目のはずです.2つの言い方はwikiの解釈を見ることができます.https://en.wikipedia.org/wiki/Spurious_wakeup.
A spurious wakeup happens when a thread wakes up from waiting on a condition variable that's been signaled, only to discover that the condition it was waiting for isn't satisfied
虚偽の起動を意味する場合、条件変数がトリガーされたために待機中のスレッドが起動すると、待機中の条件(共有データ)が満たされていない(つまり共有データがない)ことがわかります.
これは比較的直観的で正統な解釈ですが、このwikiにはもう一つの言葉があります.
To allow for implementation flexibility in dealing with error conditions and races inside the operating system, condition variables may also be allowed to return from a wait even if not signaled
オペレーティングシステムに処理エラーと(スレッド)競合実装(より大きな)柔軟性を提供するために、条件変数はトリガーされなくても返すことができます.
これが2つ目の言い方の由来ですが、実際のテストから見ると(プログラムが一晩中実行されている)、このような状況はwindowsやCentosでは起こりません.同じwikiリンクで、虚偽の目覚ましについて2つ目の中に1つの言葉があります.
The Linux pthread implementation of condition variables guarantees it will not do that.
すなわち,Linuxスレッドにおける条件変数の実現保証が第2の虚偽起動を永遠に触発しないことを実証し,我々のテストを実証した.
解決策
虚偽の起動の場合,解決策は共有データを使用するたびに空であるか否かを判断し,空であるか否かを待ち続けることである.
while (g_deque.empty())
{
    g_cond.wait(lck);
}

ここではifの判断方式は使用できません.ifは一度しか実行できないので、何度も虚偽の目覚ましをかけることは解決できません.
Lambda式を使用して解決することもできます.whileサイクルを使用しないで、g_だけです.Dequeが空でない場合にtrueが返されます
g_cond.wait(lck, [] {return !g_deque.empty(); });