std::atomic în C++

Variabilă care poate fi citită și modificată sigur de mai multe fire, fără mutex.

Sintaxă
#include <atomic>
std::atomic<T> x{valoare};
T load() const;  void store(T v);
T fetch_add(T v);
bool compare_exchange_strong(T& asteptat, T nou);

Ce face std::atomic

Operațiile pe un std::atomic<T> sunt indivizibile: niciun alt fir nu poate vedea o valoare „la jumătate”, iar ++x nu poate pierde incrementări, chiar dacă mai multe fire îl apelează simultan. Pentru contoare și steaguri simple este mai rapid decât un mutex.

Funcționează cu tipuri simple: int, bool, pointeri etc. (std::atomic_int, std::atomic_bool sunt alias-uri). Operații: load, store, exchange, fetch_add/fetch_sub (returnează valoarea veche), ++, += și compare_exchange_strong (schimbă valoarea doar dacă este cea așteptată).

Un singur atomic este sigur, dar o operație formată din mai mulți pași (citește, decide, scrie) nu devine automat atomică; pentru asta ai nevoie de compare_exchange sau de un mutex.

Ce returnează

load returnează valoarea; fetch_add returnează valoarea dinainte; compare_exchange_strong returnează true dacă a făcut schimbul.

Exemple

Exemplul 1: Un contor fără mutex

#include <atomic>
#include <print>
#include <thread>
#include <vector>

int main() {
    std::atomic<int> descarcari{0};

    std::vector<std::thread> fire;
    for (int i = 0; i < 8; ++i) {
        fire.emplace_back([&descarcari] {
            for (int j = 0; j < 5'000; ++j) ++descarcari;
        });
    }
    for (auto& t : fire) t.join();

    std::println("Descărcări: {}", descarcari.load());
}

Rezultat

Descărcări: 40000

Exemplul 2: fetch_add și compare_exchange_strong

compare_exchange_strong schimbă valoarea doar dacă este cea așteptată; altfel pune în asteptat valoarea reală.

#include <atomic>
#include <print>

int main() {
    std::atomic<int> locuri{10};

    int inainte = locuri.fetch_add(5);
    std::println("înainte: {}, acum: {}", inainte, locuri.load());

    int asteptat = 15;
    bool ok = locuri.compare_exchange_strong(asteptat, 0);
    std::println("schimbat: {}, acum: {}", ok, locuri.load());

    asteptat = 15;
    ok = locuri.compare_exchange_strong(asteptat, 99);
    std::println("schimbat: {}, valoarea reală era: {}", ok, asteptat);
}

Rezultat

înainte: 10, acum: 15
schimbat: true, acum: 0
schimbat: false, valoarea reală era: 0

Greșeli frecvente

  • Folosești un int obișnuit sau un bool modificat de mai multe fire, „pentru că e doar un număr”. Este cursă de date, deci comportament nedefinit; folosește std::atomic.
  • Scrii if (x.load() > 0) x--; crezând că este sigur. Între citire și scădere alt fir poate modifica valoarea; folosește o buclă cu compare_exchange sau un mutex.

Comenzi înrudite

  • std::mutex: Încuietoare pentru date comune: un singur fir de execuție o poate ține la un moment dat.
  • std::thread: Pornește o funcție pe un fir de execuție separat, care rulează în paralel cu restul programului.
  • std::jthread: Fir de execuție care face join automat la distrugere și poate fi rugat să se oprească (stop_token).