Что такое std::condition_variable в C++?

«Что такое std::condition_variable в C++?» — вопрос из категории Многопоточность, который задают на 30% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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;
}

Важные замечания:

  1. Всегда используйте std::unique_lock с condition_variable, так как wait() должен уметь временно отпускать и снова захватывать мьютекс.
  2. Изменение условия (data_ready) и вызов notify_*() должны выполняться под защитой того же мьютекса, чтобы избежать состояния гонки, когда уведомление отправляется до того, как потребитель начал ждать (потерянное уведомление).
  3. Предикат в wait() — это не просто синтаксический сахар, а необходимая защита от ложных пробуждений, которые разрешены стандартом.