← Назад к вопросам

Что такое 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 абстракции