condition_variable: スレッド間の通知

ビジーウェイトを避け、効率的にスレッドを待機。

condition_variable
スレッド間の待機・通知。
wait
条件が満たされるまでブロック。
notify
待機中のスレッドを起こす。

条件変数

レストランの呼び出しベル (Restaurant Pager)

「まだですか?」と店員に聞き続ける(Busy Wait)のは迷惑で疲れます(CPU浪費)。条件変数は「呼び出しベル」です。ベルを持って席で寝て待ちます(スリープ)。料理ができたらベルが鳴り(notify)、初めて動き出します。

条件変数は `std::unique_lock` と組み合わせて使います。「待機(wait)」はロックを一時的に解放してスリープし、通知が来ると再びロックを取得して起き上がります。これにより、リソースを消費せずにイベントを待つことができます。

Producer-Consumer
#include <condition_variable>
#include <mutex>
#include <queue>
std::queue<int> queue;
std::mutex mtx;
std::condition_variable cv;
// プロデューサー
void producer() {
std::lock_guard<std::mutex> lock(mtx);
queue.push(42);
cv.notify_one(); // 待機中のスレッドを起こす
}
// コンシューマー
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !queue.empty(); }); // 条件を待つ
int val = queue.front();
queue.pop();
}
実行結果
producer: queue.push(42)\nconsumer: cv.wait() → 通知で起動\nval = 42
Bad
// ❌ Bad: ビジーウェイト
while (queue.empty()) {
std::this_thread::sleep_for(10ms); // CPU浪費
}
Good
// ✅ Good: condition_variable
cv.wait(lock, []{ return !queue.empty(); });

パターン

BlockingQueue
// タイムアウト付き wait
if (cv.wait_for(lock, std::chrono::seconds(1),
[]{ return !queue.empty(); })) {
// データ到着
} else {
// タイムアウト
}
// 全スレッドを起こす
cv.notify_all();
// 典型的なパターン: Producer-Consumer
class BlockingQueue {
std::queue<int> q;
std::mutex m;
std::condition_variable cv;
public:
void push(int val) {
{
std::lock_guard lock(m);
q.push(val);
}
cv.notify_one();
}
int pop() {
std::unique_lock lock(m);
cv.wait(lock, [this]{ return !q.empty(); });
int val = q.front();
q.pop();
return val;
}
};
Tip: wait には必ず条件述語を渡す(スプリアスウェイクアップ対策)。

合格ライン

wait/notify を使える
Producer-Consumer を実装できる

参考リンク

演習課題

課題1: Producer-Consumer
condition_variable を使って Producer-Consumer パターンを実装してください。
課題2: notify_all
複数の待機スレッドを notify_all で一斉に起こしてください。

次のステップ