Ответ
В современном 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(). Ответ 18+ 🔞
А, ну ты про это, про передачу данных между потоками! Да это же, блядь, вечная тема, как будто на дворе 2002-й год, а проблемы всё те же — как не наебаться с гонками данных и не получить неожиданный пиздец в три часа ночи. Слушай, тут вариантов, конечно, овердохуища, но главное — не выебываться и выбрать нормальный.
Вот смотри, самый простой и приятный способ для всяких счётчиков или флагов — это атомарные операции, std::atomic. Представь, что у тебя есть общая переменная, и к ней лезут сразу десять потоков. Без атомиков это гарантированный пиздец, данные превратятся в кашу. А так — всё чётко, как по маслу, без всяких мьютексов. Типа вот так:
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::promise и std::future. Это для разовой передачи, типа «сделай дело и верни мне результат». Удобно, но только на один раз. Если тебе нужно постоянно что-то кидать из потока в поток — это не вариант.
А теперь, внимание, самый жирный и правильный паттерн для такого обмена — потокобезопасная очередь. Это как почтовый ящик: один поток кидает в него письма (push), другой вынимает (pop). И всё это, блядь, обёрнуто в мьютекс и условную переменную, чтобы не было гонок и чтобы потребитель не тупил вхолостую. Вот смотри, как это примерно выглядит:
#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().
Суть в чём? В push мы просто лочим мьютекс, пихаем значение в обычную очередь и будим один спящий поток через cond_var_.notify_one(). В pop — ждём, пока в очереди что-то появится (это делает cond_var_.wait с лямбдой), а потом забираем это что-то и возвращаем. Красота, ёпта! Никаких гонок, потребитель спит, пока нет работы, и просыпается ровно тогда, когда она появляется. Это и есть тот самый «производитель-потребитель», на котором держится половина многопоточных программ. Главное — не выёбывайся и не пытайся придумать велосипед, этот шаблон проверен годами и слезами.