条件変数とワーカーパターン
std::condition_variableと古典的なワーカープールパターンでスレッドを協調させます。
「条件変数とワーカーパターン」はCoddyKit上の無料C++ Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC++ Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C++ Academyコースには全4レッスンが含まれています。
条件変数とは
条件変数を使うと、別のスレッドが条件の変化を通知するまでスレッドを待機させることができます。プロデューサーとコンシューマーの協調に使用します。
std::condition_variableの基本
条件変数はstd::mutexおよび述語と組み合わせて動作します。待機中のスレッドはロックを解放し、起床すると再び取得します。
#include <condition_variable>
#include <mutex>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> tasks;
bool done = false;条件を待機する
cv.wait(lock, predicate)を使用します。ミューテックスをアトミックにアンロックしてスリープし、通知されるまで待機します。起床すると再びロックを取得して述語を確認し、falseの場合は再びスリープします。
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !tasks.empty() || done; });通知
プロデューサースレッドは状態を変更した後にcv.notify_one()またはcv.notify_all()を呼び出します。状態を変更している間はロックを保持してください。
{
std::lock_guard<std::mutex> lock(mtx);
tasks.push(42);
}
cv.notify_one();プロデューサー・コンシューマーパターン
典型的な例では、プロデューサーがタスクを追加し、コンシューマーがタスクを待機します。
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !tasks.empty() || done; });
if (done && tasks.empty()) break;
int task = tasks.front();
tasks.pop();
lock.unlock();
process(task);
}
}迷惑なウェイクアップ
条件変数で待機しているスレッドは、通知がなくてもウェイクアップすることがあります。そのため、waitは述語を受け取ります。ウェイクアップするたびに条件を再確認するためです。
notify_one と notify_all
1つの待機スレッドが処理を進められる場合はnotify_oneを使用します。状態の変更がすべての待機スレッドに影響する場合(シャットダウンシグナルなど)はnotify_allを使用します。
時間制限付き待機
wait_for(lock, duration)とwait_until(lock, time_point)は、通知を受け取ったとき、またはタイムアウト後にウェイクアップします。
if (cv.wait_for(lock, 1s, [] { return ready; })) {
/* condition met */
} else {
/* timed out */
}ワーカープールの骨組み
よく使われるパターンでは、固定数のワーカースレッドが共有キューからタスクを取り出します。mutex、condition_variable、queue、atomicな停止フラグを組み合わせて構築します。
通知の取りこぼしを防ぐ
共有状態の変更中は常にミューテックスを保持し、その状態を変更してからnotify_*を呼び出してください(通知の直前にロックを解放しても問題ありませんが、順序が保証されるようにしてください)。そうしないと、待機スレッドが通知を取りこぼす可能性があります。
std::counting_semaphore (C++20)
単純なカウントベースのシグナリングには、C++20で追加されたstd::counting_semaphoreが使用できます。パターンによっては、条件変数よりも簡潔です。
デッドロックに注意
条件変数で待機している間に複数のロックを保持すると、デッドロックにつながる可能性があります。ロックは一貫した順序で取得し、複数のミューテックスにはstd::scoped_lockを使用してください。
確認問題
通知を待機したいだけの場合でも、なぜcv.wait(lock, predicate)は述語を受け取るのでしょうか。
まとめ
条件変数は、状態の変更を待機しているスレッドを調整します。迷惑なウェイクアップに対応するにはwait(lock, predicate)を使用し、ミューテックスを保持している間に状態を変更して、その後にnotify_oneまたはnotify_allを呼び出してください。
よくある質問
「条件変数とワーカーパターン」レッスンは無料ですか?
はい。「条件変数とワーカーパターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C++ Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C++ Academyコースには全4レッスンが含まれています。
「条件変数とワーカーパターン」で何を学びますか?
std::condition_variableと古典的なワーカープールパターンでスレッドを協調させます。 ブラウザで直接実行するハンズオンコードでC++ Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C++ Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC++ Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「条件変数とワーカーパターン」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC++ Academyレッスンでコードを書いて実行できますか?
はい。すべてのC++ Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。