# Atomics **Atomics** are operations on a shared variable that execute as a single, indivisible step, even when multiple threads touch the variable at the same time. Without them, an operation as simple as `count++` is really three separate machine instructions (load, increment, store), and two threads interleaving those instructions can lose an update. Atomics close that gap in hardware, using instructions like `lock xadd` or load-linked/store-conditional, instead of falling back to a [[lock|lock]] that would put a waiting thread to sleep. The C11 `` and C++11 `` headers expose this hardware support as a portable set of operations, roughly grouped into load/store, read-modify-write, and wait/notify. Every atomic operation also carries a [[memory-order]], which controls how much the compiler and CPU are allowed to reorder surrounding non-atomic accesses around it. The examples below default to `memory_order_seq_cst` for simplicity; see [[memory-order]] for the weaker, faster alternatives. ## Load and store The simplest atomic operations just read or write a value without any computation. They prevent a torn read or torn write, the case where a load or store of a multi-byte value is split across two bus transactions and a concurrent writer's update becomes visible halfway through. ```c #include atomic_int counter = 0; int read_counter(void) { return atomic_load(&counter); } void set_counter(int value) { atomic_store(&counter, value); } ``` On most mainstream architectures, aligned loads and stores of a machine word are atomic anyway. What `atomic_load`/`atomic_store` add on top is the memory-order guarantee and a guarantee the compiler will not cache the value in a register across iterations, which matters for spin loops. ## Test-and-set **Test-and-set** reads a boolean flag and unconditionally sets it to true, returning the previous value. It is one of the oldest atomic primitives (available as a single instruction on many CPUs going back decades) and is enough to build a simple [[spinlock]]: a thread spins until it observes the flag was previously false, meaning it just claimed the lock. ```c #include atomic_flag lock = ATOMIC_FLAG_INIT; void spin_lock(void) { while (atomic_flag_test_and_set(&lock)) { /* spin */ } } void spin_unlock(void) { atomic_flag_clear(&lock); } ``` `atomic_flag` is deliberately the most minimal atomic type in the standard; unlike `atomic_bool`, it is guaranteed lock-free on every conforming implementation. ## Exchange **Exchange** (`xchg`) unconditionally writes a new value into a location and returns the old one, atomically. It is a strictly more general test-and-set: instead of always setting to `true`, it sets to any value the caller chooses. ```c #include atomic_int owner = 0; int try_claim(int thread_id) { return atomic_exchange(&owner, thread_id); // returns previous owner } ``` Exchange is unconditional, which is both its strength and its weakness: it always overwrites the current value, even if another thread just wrote something the caller did not know about. When the update needs to depend on the current value, [[cas|compare-and-swap]] is the right tool instead. ## Compare-and-swap **Compare-and-swap** ([[cas|CAS]]) is the workhorse of lock-free programming. It compares a location's current value against an expected value, and only if they match does it write a new value; either way, it reports whether the swap happened. This turns "read, compute, write" into a single atomic step, at the cost of having to retry if another thread won the race. ```c #include atomic_int shared = 0; void increment(void) { int expected = atomic_load(&shared); int desired; do { desired = expected + 1; } while (!atomic_compare_exchange_weak(&shared, &expected, desired)); } ``` C11 provides both `atomic_compare_exchange_weak` and `_strong`. The `_weak` form may fail spuriously even when the value matches, which is cheaper on LL/SC architectures (ARM, RISC-V) that cannot atomically compare-and-swap in one instruction; it is meant to be called in a retry loop like the one above. `_strong` never fails spuriously but pays for that guarantee with an internal retry loop of its own, so it is the right choice when the caller cannot easily retry. ## Fetch-and-arithmetic The **fetch-and-add** family (`fetch_add`, `fetch_sub`, and their increment/decrement special cases) atomically applies an arithmetic operation and returns the value from before the operation. These map directly to `lock xadd` on x86 and are cheaper than a CAS loop because the hardware guarantees success in one instruction. ```c #include atomic_int count = 0; void record_event(void) { atomic_fetch_add(&count, 1); // atomic_fetch_sub for decrement } ``` Because the return value is the *previous* value, fetch-and-add doubles as a way to hand out unique sequence numbers: each caller gets a distinct value even under heavy contention, with no CAS retry loop needed. ## Fetch-and-bitwise **Fetch-and-and**, **fetch-and-or**, and **fetch-and-xor** apply a bitwise operation atomically, again returning the prior value. They are the natural tool for atomically setting, clearing, or flipping individual bits in a flags word shared across threads. ```c #include atomic_uint flags = 0; void set_flag(unsigned bit) { atomic_fetch_or(&flags, 1u << bit); } void clear_flag(unsigned bit) { atomic_fetch_and(&flags, ~(1u << bit)); } ``` A common mistake is assuming a read-modify-set pair like `flags |= mask` is equivalent to `atomic_fetch_or`. It is not: the plain version reads, computes, and stores in three separate steps, leaving the same race window that atomics exist to close. ## Fetch-and-reduce **Fetch-and-min** and **fetch-and-max** atomically replace a value with the minimum or maximum of itself and an argument, returning the previous value. They are less universally available in hardware than add or bitwise ops (not all ISAs have a native atomic min/max), so on some platforms the compiler lowers them to a CAS retry loop under the hood. ```c #include atomic_int running_max = INT_MIN; void observe(int value) { int prev = atomic_load(&running_max); while (value > prev && !atomic_compare_exchange_weak(&running_max, &prev, value)) { /* prev is updated by the failed CAS; retry */ } } ``` This pattern, tracking a running extremum across threads without a lock, shows up often enough in reductions that some libraries expose it as `atomic_fetch_max` directly rather than making every caller write the CAS loop by hand. ## Atomic flag and wait/notify C11 and C++20 add **wait/notify** on top of atomics, letting a thread block until a value changes instead of busy-spinning. `atomic_wait` puts the calling thread to sleep until the observed value differs from a given one; `atomic_notify_one` and `atomic_notify_all` wake up one or all waiters respectively. This gives atomics the blocking behavior of a [[sync-mutex|mutex]] and [[sync-monitor|condition variable]] without needing a separate lock object. ```c #include atomic_int ready = 0; void producer(void) { atomic_store(&ready, 1); atomic_notify_all(&ready); } void consumer(void) { int observed; while ((observed = atomic_load(&ready)) == 0) { atomic_wait(&ready, observed); } } ``` Unlike a plain spin loop, `atomic_wait` allows the OS scheduler to park the waiting thread rather than burning CPU cycles, while still avoiding the overhead of a full mutex and condition variable pair. ## Further reading The operations above are the building blocks; several larger topics deserve their own articles. [[memory-order]] covers the ordering guarantees (`relaxed` through `seq_cst`) that every atomic operation above can be tagged with. [[lock-free-queue]], [[treiber-stack]], and [[michael-scott-queue]] show how CAS loops compose into full lock-free data structures. [[aba-problem]] and [[hazard-pointer]] cover the memory-reclamation hazards that come with lock-free structures built on CAS.