haraduka's diary

やる気が欲しい

condition_variable使ったことなし。

そういえばcondition_variableとかってなんだかんだ使ったことないなぁ、と思って書きました。
condition_variableとは、"条件変数"のことで、ある条件が満たされるまでスレッドを待機させるのに使います。
同期プリミティブとか言うんだって。
これがあれば、この条件が満たされるまでは〜〜をして、あの条件が満たされるまでは〜〜をして、みたいなマルチスレッド処理が可能になります。

condition_variableを使うときに使用するのがunique_lockであり、これはlock_guardみたいにコンストラクタでlockしてデストラクタでunlockするだけ!みたいな簡単なものではなく、むしろ色々、なんでもできるようにしてしまったmutexです。
unique_lock - cpprefjp C++日本語リファレンス
ここを見ればわかる通り、所有権の移譲だとか、途中でlockしたりだとか、try_lockだとかができます。

とりあえずコードを見てみましょう。

#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <chrono>

int main()
{
    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;

    std::thread prepare_thread([&](){
        std::cout << "#prepare thread start" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        {
            std::lock_guard<std::mutex> lock(mtx);
            ready = true;
        }
        cv.notify_one();
        std::cout << "#prepare thread finished" << std::endl;
    });
 
    std::thread main_thread([&](){
        std::cout << "#main thread start" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        {
            std::unique_lock<std::mutex> uniq(mtx);
            cv.wait(uniq, [&]{return ready;});
        }
        std::cout << "#main thread finished" << std::endl;
    });

    prepare_thread.join();
    main_thread.join();
    return 0;
}

これは、prepareスレッドで準備をして、終わったよ!と通知が来たらmainの処理を実行するような感じのものです。よくありそうですね。
ここではまず、prepare_threadが3秒のスリープに入ります。次に、main_threadは1秒待って、mtxのunique_lockを取得し、condition_variableのwaitに入ります。
このwaitでは、std::unique_lockとfunctionを受け取ります。そしてunique_lockをunlockし、通知が来るまで待ちます。通知が来たときにfunctionがtrueになっていたらunique_lockをlockし、終了します。簡単ですね。
あれ?もしnotify_oneよりwaitの方が後にきちゃったら、もう通知終わってるからwaitが抜けられなくない?と思うかもしれませんが、cv.waitを呼び出した時点で一度functionを評価するので、そのときにfunctionがtrueになっていれば大丈夫です。
んんん??これはもはやreadyの存在意義がわからない…。そうなんです。これはsprious wakeupと言って、勝手にスレッドが目覚める、つまり、擬似的に通知を受け取ったような状態になってしまうことがあるらしいので、それによってwaitを抜けないようにするために必要だそうです。

ちなみにnotify_oneとnotify_allというのがあるらしいのですが、allは眠っているスレッド全てに通知をいかせるためオーバーヘッドがのるとか。oneは最大で一つのスレッドのwaitしか解放しないらしい。