Intuition The one core idea
When two workers change the same box of memory at the same time, their steps can tangle and one change silently vanishes. std::atomic is a promise that a change on that box happens as one uninterruptible blink , and that when one worker says "done," the other worker who hears "done" also sees everything packed before it.
This page assumes you know nothing . Before you can read the parent note (topic) , you need a small pile of ideas. We build them one at a time, each earning the next.
A memory cell is a labelled box in the computer that holds one number. The label is its address ; the number inside is its value . Reading = looking inside without changing it. Writing = replacing the number.
Picture a row of school lockers. Each locker has a number painted on it (the address) and a note inside (the value).
Why the topic needs this: everything atomics do is about one box that two people reach into. If you don't picture the box, "shared variable" is just words.
A thread is one independent line of execution: a worker running its own list of instructions, one after another. Two threads run at the same time , but they can pause at any point — the operating system decides when.
Intuition The picture: two hands, one locker
Imagine two workers, each following their own checklist, but both allowed to open the same locker. The danger is not that they share — it's that either may be frozen mid-step while the other acts.
The parent note's whole problem exists because a thread can be paused between its tiny steps. See std::thread and std::async for how threads are created.
counter++ is a lie of one line
One line of C++ hides several CPU steps. The CPU cannot add to a locker directly — it must:
load : copy the locker's value into a tiny fast slot (a register ).
add 1 inside the register.
store : copy the register's value back into the locker.
A register is a tiny, ultra-fast storage slot inside the CPU core . Each thread's core has its own registers, so what one thread holds in a register is invisible to the other until it's stored back to memory.
Why the topic needs this: the "lost update" bug lives in the gap between these three steps . If you think counter++ is one step, the bug is impossible to understand.
Definition Read-Modify-Write (RMW)
A read-modify-write is any operation that reads a value, changes it based on what it read, then writes it back. x++, x += 5, x = x * 2 are all RMW.
Intuition Why two RMWs collide
Both threads load 5 . Both add 1 in their private registers → both hold 6. Both store 6 . Two increments happened, but the locker only advanced by one. One update was overwritten — "lost."
Follow the figure: the red timeline shows Thread B loading 5 while Thread A is mid-flight . This interleaving is the enemy. An atomic RMW makes steps 1–2–3 an indivisible blink so no one can slip in between.
Atomic (from Greek atomos , "uncuttable") means an operation that other threads observe as fully done or not started — never caught halfway. There is no visible in-between state.
Instead of load → (pause) → add → (pause) → store, an atomic RMW is a single grab-count-replace in one motion. No thread can peek during it, so no update can be lost.
Why the topic needs this: std::atomic<T> is exactly the tool that turns the 3-step RMW into one uncuttable step.
Definition Mutex (mutual exclusion)
A mutex is a software padlock. A thread must lock it before touching the shared box and unlock it after. While locked, every other thread that wants in must wait in line .
Intuition Why we'd rather not
A mutex is correct but costs waiting: if the lock-holder is paused by the OS, everyone else is stuck. See Mutex and Lock . The whole point of atomics is to get correctness without this padlock and its waiting.
Lock-free means the operation finishes without ever grabbing an OS mutex, and it guarantees system-wide progress : at any moment, at least one thread is making forward progress. It does not promise that every thread advances — one unlucky thread can keep losing its CAS retry and be starved indefinitely while others succeed.
Intuition The three non-blocking progress guarantees
These are ranked from weakest to strongest — read them as promises about who is guaranteed to make progress:
Obstruction-free (weakest): a thread finishes if it runs alone long enough (no interference). Others may cause it to abort and retry.
Lock-free (middle): some thread always progresses, so the system never freezes — but individuals may starve.
Wait-free (strongest): every thread finishes in a bounded number of its own steps, no matter what others do. No starvation possible.
Why the topic needs this: "lock-free operations" is literally the title. It's the payoff — atomicity without the padlock's waiting — but you must know it is not the same as "no thread ever waits" (that is wait-free).
A cache is a small fast copy of memory kept next to each CPU core . Cores prefer their own cache copy over slow main memory.
Definition The four MESI states
Each cache line copy a core holds is tagged with one of four states — the name MESI is these four letters:
Modified (M) : this core's copy is changed and is the only valid copy; main memory is stale.
Exclusive (E) : this core has the only cached copy, unchanged and matching memory. It may write freely (silently becoming M).
Shared (S) : several cores hold a read-only identical copy.
Invalid (I) : this copy is stale/unusable — must be re-fetched before use.
Intuition How an atomic RMW moves through MESI
To do an atomic increment, a core must be able to write , which needs the line in M or E — i.e. it must be the sole owner. If another core holds it S or M , the writing core broadcasts an invalidate : every other copy flips to I , ownership transfers, and only then does the RMW proceed. Because the whole load-modify-store happens while this one core holds exclusive ownership, no other core can slip in — that is the hardware giving us atomicity.
See Cache Coherency MESI and False Sharing . Why the topic needs this: this exclusive-ownership grab (an E/M state guarded by invalidations) is how the hardware provides atomicity with no software lock.
Definition Compare-And-Swap
Compare-And-Swap is a single atomic instruction meaning: "only write my new value if the box still holds the exact value I expected — otherwise tell me what it holds now."
Intuition Why "compare" then "swap"
CAS is optimistic: read the value, compute your change, then swap only if nobody touched it meanwhile . If someone did, the compare fails, you learn the fresh value, and you retry. That retry loop is the heart of every lock-free structure. See Compare-And-Swap .
Memory order is the rule controlling what other writes become visible around an atomic operation. Atomicity stops torn values; memory order controls the visibility and sequencing of the writes surrounding it.
Definition The full set of C++ memory orders
std::atomic operations accept one of these, from cheapest/weakest to safest/strongest:
==memory_order_relaxed==: atomicity only — no ordering of surrounding memory at all. Use for a pure counter whose value nobody's ordering depends on.
==memory_order_acquire== (on a load ): no read/write written after it may be reordered before it. It "subscribes" to a matching release.
==memory_order_release== (on a store ): no read/write written before it may be reordered after it. It "publishes" all prior writes.
==memory_order_acq_rel==: for a single RMW (like fetch_add) that is both a load and a store — it acts as acquire on the read half and release on the write half at once.
==memory_order_consume==: a rarely-used, weaker acquire that only orders things data-dependent on the loaded value. In practice compilers promote it to acquire; treat it as "avoid unless expert."
==memory_order_seq_cst== (the default ): sequential consistency — all seq_cst operations across all threads share one single global total order. Safest, easiest to reason about, and what you get if you pass no order argument.
Intuition The sealed package
A release store is like sealing a box and putting a "DONE" note on top: everything you packed is guaranteed in before the note. An acquire load is like reading that note: once you see "DONE," you also see everything inside. This acquire/release handshake is defined by the C++ Memory Model (C++) .
Common mistake The default is not free
The default seq_cst is the safest but often the slowest (it can force extra CPU fences). Dropping to relaxed is correct only when no other variable's visibility depends on this op's ordering — e.g. a standalone hit counter.
Why the topic needs this: correctness across threads needs both atomicity (§5) and ordering (this). One without the other still lets bugs through.
Read the map bottom-up: the ideas at the top are the raw building blocks, and every arrow means "is needed to understand." A memory cell plus a thread and its private register give you the three-step read-modify-write , whose interleaving causes the lost-update bug . That bug forces the idea of an atomic (indivisible) operation. Atomicity, combined with wanting to escape the mutex padlock , and made possible by cache/MESI ownership, yields lock-free operation and the compare-and-swap gate. Finally atomicity pairs with memory order for cross-thread visibility. All of these arrows converge on the std::atomic topic node — the parent note you are preparing to read.
Test yourself — reveal only after answering.
What is stored in a memory cell, and what labels it? A value (a number) inside; an address labels it.
Why can two threads collide even on simple code? The OS can pause a thread between the tiny machine steps of one line.
Name the three steps hidden inside counter++. load into a register, add 1, store back to memory.
What is a register and who can see it? A tiny fast slot inside a CPU core; private to that core until stored back.
Define a read-modify-write and give one example. An op that reads, changes based on the read, then writes back — e.g. x++.
What does "atomic" guarantee about intermediate states? None are visible; the op is seen as fully done or not started.
What cost does a mutex add that atomics try to avoid? Waiting in line while another thread holds the lock.
What progress does lock-free guarantee, and what does it NOT? Some thread always progresses (system-wide); it does NOT prevent an individual thread from starving.
Rank obstruction-free, lock-free, wait-free from weakest to strongest. obstruction-free < lock-free < wait-free (wait-free bounds every thread's own steps).
Name the four MESI states. Modified, Exclusive, Shared, Invalid.
Which MESI state(s) let a core write, and what does it broadcast to get there? Modified or Exclusive; it broadcasts an invalidate so other copies become Invalid.
State what CAS does when the box no longer holds expected. It does not write; it loads the current value into expected and returns false.
What are the six C++ memory orders, and which is the default? relaxed, consume, acquire, release, acq_rel, seq_cst — default is seq_cst.
What does memory order control that atomicity alone does not? The visibility and ordering of surrounding non-atomic writes across threads.