std::condition_variable în C++

Permite unui fir să aștepte, fără să consume procesor, până când alt fir îl anunță că o condiție s-a îndeplinit.

Sintaxă
#include <condition_variable>
std::condition_variable cv;
cv.wait(unique_lock, conditie);
cv.notify_one();
cv.notify_all();

Ce face std::condition_variable

Un fir care așteaptă date de la alt fir nu trebuie să verifice într-o buclă la nesfârșit. Cu std::condition_variable, firul care așteaptă apelează cv.wait(blocare, conditie): eliberează mutexul și „doarme” până când este anunțat și condiția devine adevărată.

Firul care produce datele le modifică sub același mutex, apoi apelează cv.notify_one() (trezește un fir) sau cv.notify_all() (le trezește pe toate).

Varianta cu condiție (predicat) este obligatorie în practică: un fir se poate trezi și fără să fie anunțat („trezire falsă”), iar predicatul face ca wait să verifice din nou și să se culce la loc dacă nu are motiv să continue.

Parametri

Parametru Descriere
unique_lock Un std::unique_lock<std::mutex> ocupat; wait îl eliberează cât timp așteaptă.
conditie Funcție fără parametri care returnează true când firul poate continua.

Ce returnează

wait și notify_* nu returnează nimic (wait_for returnează dacă a expirat timpul).

Exemple

Exemplul 1: Producător și consumator

Producătorul pune comenzi într-o coadă, consumatorul le preia în ordine. Doar consumatorul afișează, deci ordinea mesajelor este mereu aceeași.

#include <condition_variable>
#include <mutex>
#include <print>
#include <queue>
#include <string>
#include <thread>

std::mutex m;
std::condition_variable cv;
std::queue<std::string> coada;
bool terminat = false;

void consumator() {
    while (true) {
        std::unique_lock blocare(m);
        cv.wait(blocare, [] { return !coada.empty() || terminat; });
        if (coada.empty()) break;   // terminat și nu mai e nimic de procesat
        std::string comanda = coada.front();
        coada.pop();
        blocare.unlock();
        std::println("Procesez: {}", comanda);
    }
}

int main() {
    std::thread t(consumator);

    for (std::string c : {"pizza", "salată", "supă"}) {
        {
            std::lock_guard blocare(m);
            coada.push(c);
        }
        cv.notify_one();
    }
    {
        std::lock_guard blocare(m);
        terminat = true;
    }
    cv.notify_one();

    t.join();
    std::println("Toate comenzile au fost procesate");
}

Rezultat

Procesez: pizza
Procesez: salată
Procesez: supă
Toate comenzile au fost procesate

Exemplul 2: Start comun pentru mai multe fire

Toate firele așteaptă semnalul de start; notify_all le trezește deodată. Rezultatele sunt afișate după join.

#include <condition_variable>
#include <mutex>
#include <print>
#include <thread>
#include <vector>

int main() {
    std::mutex m;
    std::condition_variable cv;
    bool start = false;
    std::vector<int> pornite(3, 0);

    std::vector<std::thread> alergatori;
    for (int i = 0; i < 3; ++i) {
        alergatori.emplace_back([&, i] {
            std::unique_lock blocare(m);
            cv.wait(blocare, [&] { return start; });
            pornite[i] = 1;
        });
    }

    {
        std::lock_guard blocare(m);
        start = true;
    }
    cv.notify_all();
    for (auto& t : alergatori) t.join();

    std::println("Fire pornite: {}", pornite);
}

Rezultat

Fire pornite: [1, 1, 1]

Greșeli frecvente

  • Apelezi wait fără predicat. Din cauza trezirilor false, sau dacă notificarea a venit înainte ca firul să înceapă să aștepte, firul poate continua fără date sau poate aștepta pentru totdeauna.
  • Modifici condiția (de exemplu terminat = true) fără să ții mutexul. Firul care așteaptă poate rata modificarea; schimbă starea sub mutex, apoi apelează notify.

Comenzi înrudite

  • std::unique_lock: Blocare de mutex flexibilă: poate fi eliberată și reluată, amânată, mutată; necesară pentru condition_variable.
  • std::mutex: Încuietoare pentru date comune: un singur fir de execuție o poate ține la un moment dat.
  • std::jthread: Fir de execuție care face join automat la distrugere și poate fi rugat să se oprească (stop_token).
  • std::future: Un rezultat care va fi disponibil în viitor (de la std::async sau std::promise); get() îl așteaptă și îl returnează.