← Назад к вопросам
Что такое std::atomic? Когда его использовать?
2.0 Middle🔥 201 комментариев
#Многопоточность и синхронизация#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое std::atomic? Когда его использовать?
std::atomic - это одно из самых важных средств для многопоточного программирования в C++. Это шаблонный класс, который обеспечивает атомарные операции - операции, которые выполняются неделимо, без возможности прерывания другим потоком.
Что такое атомарность?
Атомарная операция - это операция, которая либо полностью выполнена, либо не выполнена вообще, без промежуточных состояний:
int counter = 0;
// Неатомарно:
counter++; // На самом деле: load -> increment -> store (3 операции)
// Между ними другой поток может прочитать counter
std::atomic<int> atomicCounter(0);
atomicCounter++; // Гарантированно атомарно
На уровне CPU это выглядит так:
// Обычная переменная:
// Шаг 1: MOV EAX, [адрес counter] // Прочитать
// Шаг 2: ADD EAX, 1 // Инкрементировать
// Шаг 3: MOV [адрес counter], EAX // Записать
// Между шагами другой поток может изменить counter!
// std::atomic<int>:
// LOCK INC [адрес counter] // Одна атомарная инструкция
// LOCK префикс = атомарность
Основные методы std::atomic
std::atomic<int> x(5);
// Чтение
int val = x.load(); // Атомарное чтение
int val2 = x; // Тоже атомарно (overload оператора)
// Запись
x.store(10); // Атомарная запись
x = 10; // Тоже атомарно
// Обмен (Exchange)
int old = x.exchange(20); // Запишет 20, вернет 10
// Compare-and-swap (CAS)
bool success = x.compare_exchange_strong(20, 30); // Если 20, то 30
bool success = x.compare_exchange_weak(20, 30); // Может ложно вернуть false
// Для целых чисел:
x++; // Инкремент
x--; // Декремент
x += 5; // Add
x -= 3; // Subtract
x |= 0xFF; // Побитовое ИЛИ
x &= 0x0F; // Побитовое И
x ^= 0xFF; // Побитовое XOR
Memory Ordering
Стд::atomic поддерживает различные модели памяти, влияющие на производительность:
std::atomic<int> x;
// memory_order_relaxed - самая быстрая, без гарантий синхронизации
x.store(5, std::memory_order_relaxed);
// memory_order_release - гарантирует видимость для acquire
x.store(5, std::memory_order_release);
// memory_order_acquire - видит все release в других потоках
int val = x.load(std::memory_order_acquire);
// memory_order_acq_rel - и acquire, и release
x.exchange(5, std::memory_order_acq_rel);
// memory_order_seq_cst - последовательная консистентность (по умолчанию)
// Самая безопасная, но медленнее
int val = x.load(); // По умолчанию seq_cst
Когда использовать std::atomic
1. Флаги управления потоками
class Worker {
private:
std::atomic<bool> shouldStop(false);
public:
void stop() {
shouldStop.store(true);
}
void run() {
while (!shouldStop.load()) {
// Работа
processWork();
}
}
};
2. Счетчики в многопоточной среде
class RequestCounter {
private:
std::atomic<long long> totalRequests(0);
public:
void recordRequest() {
totalRequests++; // Безопасно из разных потоков
}
long long getCount() const {
return totalRequests.load();
}
};
3. Простые синхронизации без lock'ов
std::atomic<bool> dataReady(false);
std::vector<int> data;
// Поток 1
void producer() {
data = {1, 2, 3, 4, 5};
dataReady.store(true, std::memory_order_release);
}
// Поток 2
void consumer() {
while (!dataReady.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
// data готов к использованию
processData(data);
}
4. Reference counting без мьютекса
class SharedResource {
private:
std::atomic<int> refCount(1);
public:
void addRef() {
refCount++;
}
void release() {
if (--refCount == 0) {
delete this;
}
}
};
Когда НЕ использовать std::atomic
1. Сложная синхронизация
// ПЛОХО: Атомарные операции над несвязанными данными
std::atomic<int> a, b;
a.store(10);
b.store(20);
// Нельзя гарантировать, что a==10 И b==20 одновременно
// ХОРОШО: Используйте mutex
std::mutex mtx;
int a, b;
std::lock_guard<std::mutex> lock(mtx);
a = 10;
b = 20;
2. Когда нужны условные переменные
// ПЛОХО: Busy-wait с atomic
std::atomic<bool> ready(false);
while (!ready.load()) {
std::this_thread::yield(); // Расходует CPU
}
// ХОРОШО: Используйте condition_variable
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
Практический пример: Thread Pool
class ThreadPool {
private:
std::atomic<bool> shutdown(false);
std::atomic<int> activeWorkers(0);
std::queue<std::function<void()>> tasks;
std::mutex tasksMtx;
std::condition_variable cv;
public:
void addTask(std::function<void()> task) {
{
std::lock_guard<std::mutex> lock(tasksMtx);
tasks.push(task);
}
cv.notify_one();
}
void shutdown() {
shutdown.store(true, std::memory_order_release);
cv.notify_all();
}
void worker() {
while (!shutdown.load(std::memory_order_acquire)) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(tasksMtx);
cv.wait(lock, [this] {
return !tasks.empty() || shutdown.load();
});
if (tasks.empty()) continue;
task = std::move(tasks.front());
tasks.pop();
}
activeWorkers.fetch_add(1);
task();
activeWorkers.fetch_sub(1);
}
}
};
Performance Considerations
// memory_order_seq_cst (по умолчанию) - медленнее
std::atomic<int> x;
x.store(5); // Full memory barrier
// memory_order_relaxed - быстрее, но опаснее
x.store(5, std::memory_order_relaxed); // Нет barriers
// На практике:
// seq_cst: ~10-20 циклов CPU (с barrier)
// relaxed: ~1-2 цикла CPU (без barrier)
Ключевые выводы
- std::atomic позволяет безопасно работать с переменными из разных потоков
- Используйте для простых флагов, счетчиков и reference counting
- Для сложной синхронизации используйте mutex + condition_variable
- Будьте внимательны с memory_order для производительности
- Предпочитайте relaxed порядок, если не нужна глобальная синхронизация
- std::atomic - это low-level инструмент; для высокоуровневой синхронизации используйте higher-level абстракции