Ответ
std::condition_variable — это примитив синхронизации из стандартной библиотеки C++ (<condition_variable>), который позволяет одному или нескольким потокам ожидать (блокироваться) до тех пор, пока не будет выполнено некоторое условие, или пока их не уведомит другой поток. Он всегда используется в паре с std::mutex для защиты общего условия от гонок данных.
Основные методы:
wait(std::unique_lock<std::mutex>& lock, Predicate pred): Блокирует текущий поток. Автоматически освобождаетlockи ждет уведомления. После пробуждения повторно захватываетlockи проверяет предикатpred. Если предикат возвращаетfalse, поток снова засыпает. Это защищает от ложных пробуждений (spurious wakeups).notify_one(): Пробуждает один из ожидающих потоков (если такие есть).notify_all(): Пробуждает все ожидающие потоки.
Типичный паттерн "Ожидание условия":
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
int shared_data = 0;
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
// Ждем, пока флаг data_ready не станет true.
cv.wait(lock, []{ return data_ready; });
// После пробуждения мьютекс снова захвачен, данные безопасны.
std::cout << "Consumer received: " << shared_data << 'n';
}
void producer() {
// Имитируем подготовку данных
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lock(mtx);
shared_data = 42;
data_ready = true;
std::cout << "Producer sent data.n";
} // Мьютекс освобождается здесь
cv.notify_one(); // Уведомляем потребителя
}
int main() {
std::thread t1(consumer);
std::thread t2(producer);
t1.join();
t2.join();
return 0;
}
Важные замечания:
- Всегда используйте
std::unique_lockсcondition_variable, так какwait()должен уметь временно отпускать и снова захватывать мьютекс. - Изменение условия (
data_ready) и вызовnotify_*()должны выполняться под защитой того же мьютекса, чтобы избежать состояния гонки, когда уведомление отправляется до того, как потребитель начал ждать (потерянное уведомление). - Предикат в
wait()— это не просто синтаксический сахар, а необходимая защита от ложных пробуждений, которые разрешены стандартом.
Ответ 18+ 🔞
А, condition_variable, ёпта! Ну это ж классика, ебать мои старые костыли. Представь себе такую картину: у тебя есть поток, который сидит и ждёт, как дурак, пока ему не принесут пиццу. А другой поток — это курьер, который её готовит. Так вот std::condition_variable — это такая дверь с глазком, в которую твой ждущий поток смотрит и орёт: «Ну чё там, готово, блядь?!»
Работает это всегда в паре с мьютексом (std::mutex), потому что иначе начнётся пиздец: пока один смотрит в глазок, другой может в дверь пиццей въехать, и все данные разъебутся.
Основные фишки:
wait(lock, pred)— это когда поток говорит: «Ладно, я пока посплю, разбудите, когда будет готово». Он отпускает мьютекс (чтобы другие могли зайти на кухню-то, данные поменять) и отрубается. Аpred— это такая хитрая жопа, проверка. Потому что стандарт разрешает потоку проснуться просто так, от ветра, это называется «ложное пробуждение». Поэтому проснулся — сразу смотри: «А пицца-то моя приехала? Нет? Ну тогда пошёл нахуй, сплю дальше».notify_one()— это тихонько пошептать одному спящему: «Эй, чувак, может, твоя пицца готова?». Разбудит только одного.notify_all()— это встать посреди комнаты и орать: «ВСЕМ ПИЦЦА, БЛЯДЬ!». Проснутся все, но тот, чья пицца готова, её заберёт, а остальные снова уснут, материясь.
Типичная схема, когда один ждёт, другой делает:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::mutex mtx; // Замок на кухне
std::condition_variable cv; // Глазок в дверь
bool data_ready = false; // Флаг "пицца готова"
int shared_data = 0; // Сама пицца
void consumer() {
std::unique_lock<std::mutex> lock(mtx); // Захватил кухню, смотрю
// Жду, пока флаг не станет true. Лямбда — это проверка на готовность.
cv.wait(lock, []{ return data_ready; });
// О, проснулся, мьютекс снова у меня в лапах, можно безопасно жрать!
std::cout << "Consumer received: " << shared_data << 'n';
}
void producer() {
// Имитируем, что готовим пиццу (задержка)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lock(mtx); // Захватываем кухню, чтобы положить пиццу
shared_data = 42; // Кладём пиццу
data_ready = true; // Ставим флажок "ГОТОВО"
std::cout << "Producer sent data.n";
} // Здесь lock_guard разрушается и отпускает мьютекс
cv.notify_one(); // Стучим в глазок: «Эй, жирный, твоя пицца!»
}
int main() {
std::thread t1(consumer);
std::thread t2(producer);
t1.join();
t2.join();
return 0;
}
Важные моменты, где все ебутся:
- Используй ТОЛЬКО
std::unique_lock. Потому чтоwait()должен уметь отпустить мьютекс на время сна, а потом схватить его обратно.lock_guardна это не способен, он тупой как пробка. - Менять общее условие (
data_ready) и оратьnotify_*()нужно обязательно под тем же мьютексом. Иначе будет race condition: представь, ты поставил флагdata_ready = trueи крикнулnotify_one(), а потребитель в этот момент ещё даже не начал ждать и не услышит крик. Уведомление потеряется, и он будет спать вечно, ядрёна вошь! - Предикат в
wait()— это не прихоть, а суровая необходимость. Потому что потоки могут просыпаться сами по себе, без всякогоnotify. Это как если бы тебя ночью разбудил соседский кот. Ты встаёшь, смотришь — пиццы нет. И идёшь дальше спать. Без предиката ты бы подумал, что пицца пришла, и пошёл бы есть воздух.