5.2.26C++ Programming

std - atomic — lock-free operations

2,056 words9 min readdifficulty · medium1 backlinks

WHAT is the problem we are solving?


HOW does the hardware make RMW atomic?

The fundamental primitive everything else is built from is Compare-And-Swap (CAS):


Figure — std - atomic — lock-free operations

Memory ordering — the second guarantee

Atomicity stops torn values. Memory order controls what other writes become visible around the atomic op.


Worked example: a lock-free counter



Recall Feynman: explain to a 12-year-old

Imagine a shared piggy bank and two kids both adding a coin. If they both peek inside ("5 coins"), each thinks "now 6", and both write 6 on the label — one coin's count vanished. An atomic operation is like a magic hand that grabs, counts, and updates in one blink so no one can peek mid-way. Lock-free means we don't need to put a padlock on the bank (no waiting in line) — the magic is built into the bank itself. And memory order is the rule that says: if I leave a note "I'm done" on the box, anyone who reads that note also sees everything I packed inside.


Flashcards

What does std::atomic<T> guarantee that a plain T does not?
Operations are indivisible (atomic) and follow a chosen memory order, preventing torn reads and lost updates across threads.
Why is plain counter++ unsafe across threads?
It is a load–add–store sequence; threads can interleave between the steps and lose updates.
Define "lock-free".
An operation that completes in bounded steps without acquiring an OS mutex, usually via one CPU instruction (LOCK XADD / CMPXCHG).
What does compare_exchange_strong(expected, desired) do?
Atomically: if value==expected set it to desired and return true; else load current value into expected and return false.
Why use a CAS loop for an arbitrary RMW?
It writes only if no one changed the value since the read; on failure it refreshes and retries, giving lock-free progress.
Difference between compare_exchange_weak and _strong?
weak may fail spuriously (cheaper, use inside loops); strong won't fail spuriously (use for single non-looped checks).
What pairing creates a synchronizes-with edge?
A release store in one thread observed by an acquire load in another.
When is memory_order_relaxed appropriate?
When you need atomicity only (e.g. a counter) and no other variable's visibility depends on this op's ordering.
Trap: is a = a + 1 atomic for atomic<int> a?
No — it's a separate atomic load then atomic store; use a.fetch_add(1) or ++a.
How do you check lock-freedom at compile time?
std::atomic<T>::is_always_lock_free (or is_lock_free() at runtime).
What is the default memory order if unspecified?
memory_order_seq_cst (sequentially consistent, safest, slowest).

Connections

  • Mutex and Lock — the blocking alternative to lock-free atomics
  • Memory Model (C++) — happens-before, synchronizes-with
  • Cache Coherency MESI — why LOCK-prefixed instructions cost a cache line
  • std::thread and std::async — where these races arise
  • Compare-And-Swap — universal primitive for lock-free data structures
  • False Sharing — performance trap of atomics on the same cache line

Concept Map

interleave

three steps not indivisible

makes indivisible

when no mutex

holds cache line

provides atomicity for

universal primitive of

builds any

writes only if unchanged

one thread wins

ensures

Two threads shared memory

Read-modify-write x++

Lost update bug

std::atomic T

Lock-free

LOCK XADD hardware

Compare-And-Swap

CAS retry loop

Optimistic concurrency

Progress guaranteed

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, jab do threads ek hi variable ko ek saath chhoote hain, tab problem hoti hai. counter++ dikhta toh simple hai par andar se woh teen steps hai — value load karo, +1 karo, wapas store karo. Agar do threads beech mein ghus jaaye, toh dono 5 load kar lete hain, dono 6 likh dete hain — ek increment gayab! Isko bolte hain lost update. std::atomic<T> yeh problem solve karta hai: woh operation ko indivisible bana deta hai, matlab beech mein koi ghus hi nahi sakta.

Lock-free ka matlab hai ki yeh kaam bina kisi mutex (taala) ke ho jaata hai — CPU ke paas ek hi instruction hota hai (jaise LOCK XADD) jo poora load-add-store ek jhatke mein kar deta hai. Isliye threads ko line mein khade hone ki zaroorat nahi, bahut fast. Sabse important tool hai CAS (compare-and-swap): "agar value abhi bhi wahi hai jo maine padhi thi, toh nayi value daal do, warna fail batao aur main retry kar lunga." Isi CAS loop se hum lock-free stack, queue sab bana lete hain.

Doosri cheez hai memory order. Atomicity sirf torn value rokti hai, par doosre variables ki visibility control karne ke liye acquire/release use karte hain. Producer release se "publish" karta hai, consumer acquire se "subscribe" — yeh ek gate banata hai jisse pehle ke saare writes guarantee ho jaate hain. Simple counter ke liye relaxed enough hai (sabse sasta), par jab doosre data ki ordering important ho tab acquire/release lagao. Default seq_cst hota hai — safe par slow.

Ek galti se bacho: a = a + 1 atomic variable par bhi safe nahi hai, kyunki woh do alag atomic ops hai (load phir store). Hamesha a.fetch_add(1) ya ++a member operator use karo. Yeh chhoti baat exam aur real code dono mein bahut bar galat hoti hai.

Go deeper — visual, from zero

Test yourself — C++ Programming

Connections