Как можно обмениваться информацией между потоками в C++?

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

Ответ

В современном C++ есть несколько безопасных способов обмена данными между потоками:

  • Атомарные операции (std::atomic): Для простых типов данных (int, bool, указатели). Гарантируют отсутствие гонок данных без использования мьютексов.
    std::atomic<int> shared_counter{0};
    // Поток 1:
    shared_counter.fetch_add(1, std::memory_order_relaxed);
    // Поток 2:
    int value = shared_counter.load(std::memory_order_acquire);
  • Мьютексы и условные переменные (std::mutex, std::condition_variable): Для защиты критических секций и организации ожидания событий.
  • Очереди (std::queue + мьютекс или std::priority_queue): Классический шаблон "производитель-потребитель".
  • Каналы (std::promise/std::future): Для однократной передачи результата или исключения из одного потока в другой.
  • Потокобезопасные контейнеры: Если используется сторонняя библиотека вроде Intel TBB или самостоятельно написанные обертки.

Пример: потокобезопасная очередь для обмена сообщениями:

#include <queue>
#include <mutex>
#include <condition_variable>

template<typename T>
class ThreadSafeQueue {
    std::queue<T> queue_;
    mutable std::mutex mutex_;
    std::condition_variable cond_var_;
public:
    void push(T value) {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(std::move(value));
        cond_var_.notify_one();
    }
    T pop() {
        std::unique_lock<std::mutex> lock(mutex_);
        cond_var_.wait(lock, [this]{ return !queue_.empty(); });
        T value = std::move(queue_.front());
        queue_.pop();
        return value;
    }
};
// Использование: один поток push(), другой — pop().