Skip to content
  • Atomics & the Memory Model · C++

Atomics & the Memory Model

LearnViaNotes @learnvianotes· 2 min read

std::atomic<T> makes individual reads, writes and read-modify-write operations indivisible — lock-free synchronisation for counters, flags and pointers.

Atomic counter

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

int main() {
    std::atomic<int> hits{0};
    {
        std::vector<std::jthread> ts;
        for (int t = 0; t < 8; ++t)
            ts.emplace_back([&] { for (int i = 0; i < 10'000; ++i) hits.fetch_add(1); });   // or ++hits
    }
    std::cout << hits.load() << ' ' << hits.is_lock_free() << '\n';

    std::atomic<int> value{5};
    int expected = 5;
    bool swapped = value.compare_exchange_strong(expected, 10);   // CAS: if value == 5, set 10
    std::cout << swapped << ' ' << value << '\n';
    expected = 5;
    swapped = value.compare_exchange_strong(expected, 20);         // fails; expected becomes 10
    std::cout << swapped << ' ' << expected << '\n';
    std::cout << value.exchange(0) << ' ' << value << '\n';
}
Output
80000 1
1 10
0 10
10 0
OperationMeaning
load() / store(v)Atomic read / write
fetch_add, fetch_sub, ++, --, +=Atomic read-modify-write; return old (fetch_) or new value
exchange(v)Set and return the old value
compare_exchange_strong/weak(expected, desired)CAS — the building block of lock-free algorithms
std::atomic_flagGuaranteed lock-free boolean (spin locks)

Publishing data with a flag

C++
#include <atomic>
#include <iostream>
#include <string>
#include <thread>

int main() {
    std::string payload;
    std::atomic<bool> ready{false};
    std::jthread producer([&] {
        payload = "config loaded";                        // 1. write the data
        ready.store(true, std::memory_order_release);     // 2. publish
    });
    while (!ready.load(std::memory_order_acquire)) {}      // 3. wait (spin) for the flag
    std::cout << payload << '\n';                          // 4. guaranteed to see the write
}
Output
config loaded

The default ordering, memory_order_seq_cst, is the easiest to reason about. release/acquire pairs are cheaper on some CPUs: everything written before the release-store is visible after the matching acquire-load. relaxed guarantees atomicity only — fine for statistics counters.

std::mutexstd::atomic
ProtectsAny block of code / several variablesOne variable
BlockingYes (threads sleep)No (lock-free for built-in sizes)
Best forInvariants spanning multiple fieldsCounters, flags, single pointers

Is ++x on a std::atomic<int> thread-safe? What about x = x + 1?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report