Что такое std::atomic в C++?

Ответ

std::atomic — это шаблонный класс из стандартной библиотеки C++ (<atomic>), который обеспечивает атомарный (неделимый) доступ к переменной в многопоточном контексте. Это означает, что операции чтения, записи и модификации над такой переменной защищены от состояний гонки (data races).

Основные принципы:

  • Атомарность: Операции, такие как load, store, fetch_add, выполняются как единое целое относительно других потоков.
  • Порядок памяти: Позволяет задавать модель памяти (memory_order), контролируя видимость изменений между потоками и строгость упорядочивания операций.
  • Применимость: Специализирован для базовых типов (int, bool, указателей), но может быть использован с пользовательскими типами, если они тривиально копируемы (с некоторыми ограничениями на производительность).

Пример: потокобезопасный счётчик

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

std::atomic<int> counter{0};

void increment(int iterations) {
    for (int i = 0; i < iterations; ++i) {
        // Атомарно добавляем 1. Используем relaxed ordering,
        // так как важен только атомарный инкремент.
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::thread t1(increment, 1000000);
    std::thread t2(increment, 1000000);
    t1.join();
    t2.join();
    // Гарантированно выведет 2000000, состояние гонки исключено.
    std::cout << "Final counter value: " << counter.load() << 'n';
    return 0;
}

Почему это важно: Использование std::atomic для простых операций часто эффективнее, чем блокировка мьютексом, так как реализуется через атомарные инструкции процессора.

Ответ 18+ 🔞

Давай разберем эту штуку, а то голова пухнет. std::atomic — это такая хитрая жопа из стандартной библиотеки C++ (<atomic>), которая делает так, чтобы с одной переменной одновременно не лезли все потоки, как гомосеки налетели, и не превращали твои данные в кашу.

Суть, если по-простому:

  • Атомарность, ёпта: Это значит, что операции вроде чтения, записи или fetch_add происходят за один неделимый такт. Другой поток не влезет посередине и не увидит какую-то ересь. Никаких состояний гонки — всё чётко.
  • Порядок в памяти: Тут можно тонко настраивать, как потоки будут видеть изменения друг друга, с помощью этих самых memory_order. Можно сделать максимально строго, а можно и расслабиться для скорости, если знаешь, что делаешь.
  • Где юзать: Идеально для простых типов — int, bool, указателей. Для своих структур тоже можно, но если они тривиально копируемые, иначе будет овердохуища проблем с производительностью.

Смотри, как это работает на живом примере: счётчик, который не сломается

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

std::atomic<int> counter{0}; // Вот наш защищённый парень

void increment(int iterations) {
    for (int i = 0; i < iterations; ++i) {
        // Атомарно прибавляем единичку. Используем relaxed, потому что
        // нам тут главное — чтобы инкремент был цельным, а не порядок операций.
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::thread t1(increment, 1000000);
    std::thread t2(increment, 1000000);
    t1.join();
    t2.join();
    // И тут, бля, будет гарантированно 2000000. Никакого пиздеца с числами.
    std::cout << "Final counter value: " << counter.load() << 'n';
    return 0;
}

А в чём, собственно, прикол? Да в том, что для таких простых действий std::atomic обычно быстрее, чем городить мьютекс. Он жрёт не твои системные вызовы, а использует специальные команды процессора. Так что если нужно просто считать что-то в потоках — это твой выбор, чувак.