Стандартная библиотека C++<condition_variable>

В многопоточном программировании синхронизация между потоками является очень важной проблемой.

Стандарт C++11 ввёл<condition_variable>Этот заголовочный файл предоставляет механизм, который позволяет потокам приостанавливаться, когда определённые условия не выполнены, до тех пор, пока другие потоки не уведомят их о том, что условия выполнены.

condition_variableЭто продвинутый инструмент для синхронизации между потоками, который безопаснее и удобнее, чем использование низкоуровневых примитивов синхронизации (таких как мьютексы и условные переменные).

condition_variableЭто шаблон класса, используемый для реализации синхронизации между потоками в многопоточной среде. Он позволяет одному или нескольким потокам ожидать, пока определённое условие станет истинным, в то время как другие потоки могут разбудить эти ожидающие потоки.

Синтаксис

Ниже приведеныcondition_variableбазовый синтаксис:

#include <condition_variable>

void notify_one() {
    // 唤醒一个等待的线程
}

void notify_all() {
    // 唤醒所有等待的线程
}

template <class Mutex>
class condition_variable {
public:
    condition_variable();
    ~condition_variable();

    void wait(unique_lock<mutex>& lock);
    void wait_for(unique_lock<mutex>& lock, chrono::duration<Rep, Period> const& rel_time);
    void wait_until(unique_lock<mutex>& lock, chrono::time_point<Clock, Duration> const& abs_time);

    void notify_one() noexcept;
    void notify_all() noexcept;
};

Пример

Ниже приведён пример использованияcondition_variableПростой пример его использования, демонстрирующий базовую реализацию задачи производителя-потребителя.

Пример

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

std::mutex mtx;
std::condition_variable cv;
std::queue<int> product;

void producer(int id) {
    for (int i = 0; i < 5; ++i) {
        std::unique_lock<std::mutex> lck(mtx);
        product.push(id * 100 + i);
        std::cout << "Производитель " << id << " произвёл " << product.back() << std::endl;
        cv.notify_one();
        lck.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lck(mtx);
        cv.wait(lck, []{ return !product.empty(); });
        if (!product.empty()) {
            int prod = product.front();
            product.pop();
            std::cout << "Потребитель " << id << " потребил " << prod << std::endl;
        }
        lck.unlock();
    }
}

int main() {
    std::thread producers[2];
    std::thread consumers[2];

    for (int i = 0; i < 2; ++i) {
        producers[i] = std::thread(producer, i + 1);
    }

    for (int i = 0; i < 2; ++i) {
        consumers[i] = std::thread(consumer, i + 1);
    }

    for (int i = 0; i < 2; ++i) {
        producers[i].join();
        consumers[i].join();
    }

    return 0;
}

Вывод этой программы может различаться, поскольку зависит от планирования потоков. Однако вы увидите, как производитель создаёт элементы, а затем потребитель потребляет их. Вывод будет похож на:

Producer 1 produced 100
Producer 2 produced 200
Consumer 1 consumed 100
Producer 1 produced 101
Consumer 2 consumed 200
Producer 2 produced 201
Consumer 1 consumed 101
...

Примечания

  • Использованиеcondition_variableПри этом необходимо убедиться, что мьютекс захвачен перед ожиданием и освобождён после пробуждения.
  • wait、wait_forиwait_untilЭти функции освобождают мьютекс, а затем повторно захватывают его во время ожидания.
  • notify_oneпробуждает один ожидающий поток, тогда какnotify_allпробуждает все ожидающие потоки.
Другие расширения