条件変数

今日は地味な実装ばかりで書くことないので、id:Cryolite:20060606 さんのところの条件変数(condition variable)について書いておきます。

条件変数とは

条件変数は、一つあるいは複数のオブジェクトの状態が特定の条件を満たすまで、スレッドを待機させるための仕組みです。
名前から連想される機能と違い、条件変数オブジェクト自身には「条件」に該当する変数は含まれません。「条件」が満たされたタイミングで通知を行うための同期オブジェクトに過ぎません。
(そもそも条件変数という名前が混乱の元だと思います。)

使い方

Boost.Threadを使った例です。

boost::mutex m;
boost::condition c;
bool v = false;

void thread_a()
{
    // 5秒スリープ
    boost::xtime xt;
    boost::xtime_get(&xt, boost::TIME_UTC);
    xt.sec += 5;
    boost::thread::sleep(xt);

    // vを変更して通知
    boost::mutex::scoped_lock lk(m);
    v = true;
    c.notify_one();
}

void thread_b()
{
    // vがtrueになるまで待つ
    boost::mutex::scoped_lock lk(m);
    while (!v)
        c.wait(lk);
}

疑問その1:なぜnotify_one()を呼ぶときにmutexをlockするのか

    {
        boost::mutex::scoped_lock lk(m);
        v = true;
    }
    c.notify_one(); // 間違い

とすると、notify_one()を読んだ時点で別のスレッドがvの値を変更している可能性があります。

疑問その2:なぜwait()にscoped_lockを渡すのか

    while (true)
    {
        {
            boost::mutex::scoped_lock lk(m);
            if (v)
                break;
        }
        // ポイントA
        c.wait(); // 間違い
    }

とすると、ポイントAの箇所でnotify_one()が呼ばれる場合があります。notify_one()による通知は、その時点でwait()を呼んでいるスレッドでのみ受け取ることができます。ですから、この場合は次のwait()は無期限にブロックしてしまいます。
また、

    boost::mutex::scoped_lock lk(m);
    while (!v)
        c.wait(); // 間違い

では、wait()中にmutexがロックされたままになり、いくら待ってもvが変更されません。
つまり、wait()で待機すると同時にmutexをアンロックしないといけないのです。このため、wait()はscoped_lockを要求するのです。

疑問その3:なぜ条件判定がwait()より先にあるのか

    // ポイントB
    boost::mutex::scoped_lock lk(m);
    do
    {
        c.wait(lk); // 間違い
    } while (!v);

ポイントBの時点でnotify_one()が呼ばれていると、wait()でブロックします。
やはり、mutexをロックしてオブジェクトの値を固定した状態で条件を確認し、条件を満たしていない場合のみwait()を呼ぶようにしなければいけません。

TODO

Win32イベントオブジェクトとの比較もやりたかったのですが、眠くなってきたのでまた今度。