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
waitfă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ă.