condition variable
概要
condition variableとは、wait()/notify()によるスレッド間同期機能を提供する。 condition variableには、std::unique_lockを受け取るcondition_variableと、BasicLockable要件を満たす任意のmutexを受け取るcondition_variable_anyがある。 機能的には変わらないので、基本的にcondition_variableを使用すれば良い。
condition_variableには、一般的なwait/notify機能が提供されている。wait機能には、通常のwait, 日時、期間指定のwaitが提供されている。 また、notify機能には、対象のcondition_variableでwaitしている内の1スレッドを起こすnotify_one(), 対象のcondition_variableでwaitしている全てのスレッドを起こすnotify_all()が提供されている。 なお、wait状態のスレッドが存在しない時にnotifyを行う場合、notifyは無視される。そのため、wait前のnotifyが発生した場合、waitによるデッドロックを注意する必要がある。
本機能を使用する場合、ヘッダファイルcondition_variableをインクルードする。
以下に使用例を記載する。
#include <thread>
#include <mutex>
#include <condition_variable>
struct X {
void f() {
std::cout << "ok" << std::endl;
}
};
X* x = nullptr;
std::mutex m;
std::condition_variable cond;
void do_notify() {
x = new X();
cond.notify_one();
}
void do_sync_process() {
std::unique_lock<std::mutex> lock(m);
cond.wait(lock);
x->f();
}
int main() {
std::thread t1(do_sync_process);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::thread t2(do_notify);
t1.join();
t2.join();
return 0;
}
実は、上記プログラムはバグを含んでいる。バグは"Spurious wakeup"という事象によって引き起こされる。 Spurious wakeupとは、waitにより待機しているスレッドが、notifyを受け取る前に起きてしまう現象である。[参考] バグを回避するためには、 Spurious wakeupが発生した場合に再度waitするように実装する、 もしくはcondition_variableが提供している 関数オブジェクトPredicate を加えたwaitを用いるようにする。 Predicateはbool値を返す関数オブジェクトで、falseを返す限り再waitが行われる。以下のような実装である。
while(!pred())
wait(lock);
以下に関数オブジェクトを用いた使用例を示す。
#include <thread>
#include <mutex>
#include <condition_variable>
struct X {
void f() {
std::cout << "ok" << std::endl;
}
};
X* x = nullptr;
bool ready=false;
std::mutex m;
std::condition_variable cond;
void do_notify() {
x = new X();
ready=true;
cond.notify_one();
}
void do_sync_process() {
std::unique_lock<std::mutex> lock(m);
cond.wait(lock, []{ return ready; } );
x->f();
}
int main() {
std::thread t1(do_sync_process);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::thread t2(do_notify);
t1.join();
t2.join();
return 0;
}