C++ std::atomic

Dictionary meaning: Atomic means something that cannot be broken down into smaller parts
atomic variable can only be changed/updated by one thread at a time. atomic variable can be shared between threads and can be updated (without the need for locks).
Only varibles can be declared as atomic, not functions or classes.
We cannot protect a multi-statement critical section using a single std::atomic the way you use a std::mutex
Same idea as Go atomics.

#include <atomic>
std::atomic<int> x{0};

Atomic Internal Implementation

Compiler injects memory barriers/fences + Hardware-level CPU instructions(at runtime)
Compiler injects memory barriers that prevents the compiler from reordering read and write operations outside atomic boundary
CPU instructions for atomicity:
  x86/x64
  LOCK XADD: For atomic additions. lock the cache line or memory bus so other cores cannot access it during the operation.
  ARM and RISC-V architectures
  LDREX/STREX: Load-Exclusive and Store-Exclusive instructions.

Example: Set, load(read)

store = set the atomic. load = read it. Default constructor leaves the value unspecified — always initialize ({0} or ATOMIC_VAR_INIT).

#include <atomic>
#include <iostream>

int main() {
    std::atomic<int> x{0};   // create, start at 0

    x.store(5);               // SET   (default: seq_cst)
    int v = x.load();         // READ  (default: seq_cst)
    std::cout << v << "\n";   // 5

    x.fetch_add(1);           // SET via add (default: seq_cst)
    std::cout << x.load() << "\n";  // 6
}

Ordering?

int x = 0;
int y = 0;

void setup() {
    x = 1; // Statement A
    y = 2; // Statement B
}

To a human, Statement A comes before Statement B. However, compilers (during optimization) may execute Statement B before Statement A(ie shuffle) as long as the final result is the same. This is called reordering.

Memory ordering for atomics

Memory order controls reordering of surrounding variables/control around the atomic. It is a hint to the compiler and CPU about how to order memory operations.
Default memory order: memory_order_seq_cst

Order Meaning When to use
memory_order_relaxed Guarantees atomicity only for that specific variable, not surrrounding variables.
memory_order_relaxed the atomic operation itself won't be torn(ie broken), but the compiler and CPU are completely free to reorder surrounding read and write operations in any way they want
memory_order_release Acts as a barrier for preceding operations. Any memory writes (to atomic or regular variables) that happened before the release store are locked in place and cannot be reordered after it.
memory_order_acquire Acts as a barrier for subsequent operations. Any memory reads or writes that happen after the acquire load cannot be reordered before it.
memory_order_acq_rel Synchronized handshake between two threads.
Weaker than sequential consistency because they don't enforce a global timeline
memory_order_seq_cst (default memory order) Strictest ordering.
Every thread in the program will agree on the exact chronological sequence in which atomic operations occurred, no matter how many CPU cores or caches are involved.
drawback: To achieve this, the CPU must inject heavy hardware barriers (fences) that stall execution until caches are fully synchronized, making it safe but potentially slower.
memory_order_consume (rarely used / discouraged) Weaker than acquire (data-dependent loads only). Compilers almost always promote it to acquire. Do not use. Prefer acquire.

Mixing 2 memory orders

Illegal combinations. Code will compile but will not work as expected.

// Typical pair (weaker than default, still correct for a flag + data)
data = 42;
ready.store(1, std::memory_order_release);

if (ready.load(std::memory_order_acquire) == 1)
    use(data);   // sees 42