В чём разница между mutex и condition variable в контексте многопоточного программирования на C++?

«В чём разница между mutex и condition variable в контексте многопоточного программирования на C++?» — вопрос из категории Многопоточность, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

std::mutex — это примитив синхронизации, обеспечивающий взаимное исключение (mutual exclusion). Его единственная цель — гарантировать, что в один момент времени критическую секцию кода выполняет не более одного потока. Он не предоставляет механизма для ожидания какого-либо условия.

std::condition_variable — это механизм оповещения между потоками. Он позволяет одному или нескольким потокам блокироваться в ожидании выполнения некоторого условия, связанного с общими данными. Ключевой момент: condition_variable всегда работает в паре с mutex.

Простая аналогия: Mutex — это ключ от комнаты (только один человек может войти). Condition Variable — это звонок будильника, который говорит ждущему снаружи человеку: "Теперь можно проверить, свободна ли комната".

Типичный шаблон использования condition variable в C++:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool finished = false;

// Поток-производитель
void producer() {
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        {
            std::lock_guard<std::mutex> lock(mtx);
            data_queue.push(i);
            std::cout << "Produced: " << i << std::endl;
        } // lock_guard выходит из области видимости, мьютекс освобождается
        cv.notify_one(); // Оповещаем одного потребителя
    }
    {
        std::lock_guard<std::mutex> lock(mtx);
        finished = true;
    }
    cv.notify_all(); // Оповещаем всех потребителей о завершении
}

// Поток-потребитель
void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        // 1. Освобождаем мьютекс и ждём сигнала.
        // 2. При получении сигнала снова захватываем мьютекс.
        // 3. Проверяем условие: если очередь не пуста ИЛИ работа завершена, выходим из ожидания.
        cv.wait(lock, [] { 
            return !data_queue.empty() || finished; 
        });

        // Если работа завершена и очередь пуста — выходим.
        if (finished && data_queue.empty()) {
            break;
        }

        // Извлекаем данные из очереди.
        int value = data_queue.front();
        data_queue.pop();
        lock.unlock(); // Явно отпускаем мьютекс перед долгой обработкой.

        std::cout << "Consumer " << id << " processed: " << value << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

int main() {
    std::thread prod(producer);
    std::thread cons1(consumer, 1);
    std::thread cons2(consumer, 2);

    prod.join();
    cons1.join();
    cons2.join();
    return 0;
}

Итоговое сравнение:

  • Mutex решает проблему конкуренции (race condition).
  • Condition Variable решает проблему координации (coordination/signaling), позволяя потокам эффективно ждать событий, не занимая CPU (busy-wait).