Question bank — std - atomic — lock-free operations
5.2.26 · D5· Coding › C++ Programming › std - atomic — lock-free operations
Picture 1 — race window (kyun plain x++ counts kho deta hai)
Neeche wali figure ek timeline hai: time left se right flow karta hai, har thread ke liye ek row hai. Red overlap band dekho — yahi woh "race window" hai jahan dono threads ne ek hi value load kar li hai jab tak kisi ne bhi store nahi kiya.

Picture 2 — acquire/release ek one-way gate ki tarah
Yeh figure dikhata hai ki kyun memory order matter karta hai. Har thread ke paas operations ka ek ordered stack hai; arrows dikhate hain ki compiler/CPU ko allowed hain kaunse reorderings (green = slide karne ke liye allowed, red = gate se blocked).

True ya false — justify karo
Kisi variable ko std::atomic<int> mark karna statement a = a + 1; ko ek single atomic step bana deta hai.
a + 1 ek atomic load hai, phir a = ... ek alag atomic store hai — do operations ke beech ek gap hai jahan koi doosra thread ghus sakta hai (bilkul Picture 1 ka race window). Ek indivisible read-modify-write ke liye a.fetch_add(1) ya ++a use karo.std::atomic hamesha lock lene se bachta hai.
CMPXCHG16B). Kisi std::atomic<BigStruct> ke liye, maan lo 40 bytes ka, koi single instruction nahi hai jo use atomically swap kar sake, isliye library ek internal mutex chhupa leti hai. Sirf woh types jahan is_always_lock_free true ho (ya is_lock_free() runtime par true return kare) lock-free guaranteed hain.Ek lock-free algorithm guarantee karta hai ki har thread progress karta hai.
Heavy contention mein ek lock-free counter hamesha mutex-protected counter se better hota hai.
memory_order_relaxed kisi operation ki atomicity ko kamzor karta hai.
Atomic operations ka default memory order relaxed hai.
memory_order_seq_cst (sequentially consistent) hai — sabse safe aur sabse slow. Aapko relaxed mein opt in karna padta hai.compare_exchange_weak buggy hai kyunki yeh fail kar sakta hai jab values match karti hain.
Ek release store akele do threads ko synchronize karta hai.
release store jo publish kare aur doosre thread par ek matching acquire load jo same value read kare (Picture 2 mein diagonal edge). Ek half akele koi happens-before edge nahi banata.memory_order_consume sirf acquire ka ek sasta synonym hai.
consume sirf un operations ko order karta hai jo loaded value par data-dependent hain (jaise abhi load kiye pointer ko dereference karna), na ki acquire ki tarah baad ke sabhi reads/writes. Yeh weakly-ordered CPUs par pointer-chasing ke liye tha, lekin ise sahi se specify karna itna mushkil hai ki current compilers ise acquire tak promote karte hain, isliye aaj practical mein isse koi speedup nahi milta.Do threads jo ek plain int par counter++ karte hain hamesha correct total denge, bas slowly.
int increments apne load–add–store steps mein interleave ho sakte hain (Picture 1), isliye updates silently lost ho jaate hain — final total aksar expected se kam hota hai, sirf late nahi. Yeh formally undefined behaviour bhi hai.is_lock_free() aur is_always_lock_free ek hi cheez ka matlab hai.
is_always_lock_free us platform par us type ke liye ek compile-time constant hai; is_lock_free() ek specific object par runtime check hai, jo over-aligned ya unusual instances ke liye matter kar sakta hai.Error dhundo
std::atomic<int> a{0};
a = a + 1; // "atomic increment"::: Comment jhooth bolta hai. Yeh a ka ek load hai plus a mein ek alag store, inke beech ek race window ke saath (Picture 1). a.fetch_add(1) ya ++a se replace karo.
std::atomic<bool> ready{false};
int data = 0;
// producer
ready.store(true, std::memory_order_relaxed);
data = 42;::: Do bugs hain: publish relaxed use karta hai (koi ordering nahi, isliye consumer ready==true dekh sakta hai data==42 se pehle), aur data = 42 flag ke baad likha gaya hai. Flag ek release store hona chahiye jo data write ke baad rakha jaye.
Node* n = new Node{v, head.load()};
head.compare_exchange_weak(n->next, n); // no loop::: compare_exchange_weak spuriously fail kar sakta hai aur real contention par bhi fail kar sakta hai, isliye ek single call silently push nahi kar sakta. Ise while(!head.compare_exchange_weak(n->next, n)){} mein wrap karna zaroori hai.
std::atomic<int> a{0};
if (a.compare_exchange_strong(expected, 5)) { /* ... */ }
// expected was never initialized::: expected ko call se pehle us value se seed karna zaroori hai jo aap sochte ho a mein hai. Uninitialized hone par, compare meaningless hai; aur failure par CAS expected ko a ki current value se overwrite kar deta hai, jise phir aapko use karna hota hai.
std::atomic<int> x{0}, y{0};
// thread A
x.fetch_add(1, std::memory_order_relaxed);
y.fetch_add(1, std::memory_order_relaxed);
// thread B expects: if it sees y==1 then x==1::: relaxed ke saath, threads ke beech do atomics ke beech koi ordering nahi hai — B y==1 observe kar sakta hai jab bhi x==0 dekh raha ho. Agar B us ordering par rely karta hai, toh release/acquire (ya seq_cst) use karo.
Why questions
Kyun LOCK prefix (jaise LOCK XADD) kisi increment ko bina kisi software lock ke atomic banata hai?
Kyun ek single CAS instruction koi bhi read-modify-write banane ke liye enough hai?
Kyun relaxed ek pure statistics counter ke liye sahi choice hai?
Kyun consumer ka flag-read ek acquire load hona chahiye?
Kyun ek release store kabhi bhi earlier writes ko uske baad "leak" nahi hone de sakta?
Kyun memory_order_consume kuch hardware par acquire se tez hone ka iraada tha?
acquire ko saari following operations ko order karne ke liye ek fence chahiye, lekin consume sirf natural data-dependency chain respect karna chahta hai (CPU already ek pointer dereference ko us load se pehle reorder nahi karega jisne pointer produce kiya). Woh fence-free path intended win tha — practically undelivered kyunki compilers abhi consume ko acquire tak upgrade karte hain.Kyun compare_exchange failure par expected ko update karta hai usse akela chhodne ki jagah?
expected mein wapas di jaati hai, desired value recompute karne ke liye ready.Kyun ek lock-free stack phir bhi performance problems suffer kar sakta hai jab bhi yeh kabhi block nahi karta?
head pointer par hammer karte hain woh cache line ko cores ke beech bounce karate hain — yeh cache-line ping-pong (False Sharing ka cousin) cost dominate kar sakta hai.Edge cases
Kya hota hai agar do threads ek saath CAS-loop stack par push call karein?
compare_exchange succeed karta hai; doosre ka fail hota hai kyunki head badal gaya, isliye n->next naye head se refresh hota hai aur woh retry karta hai. Dono pushes eventually land ho jaate hain — koi update lost nahi hota.Ek while(!head.compare_exchange_weak(n->next, n)){} push loop mein, kya hoga agar CPU pehli try par hi compare_exchange_weak ko spuriously fail kara de jab bhi kuch nahi badla?
n->next (unchanged) head se refresh hota hai aur agla attempt succeed karta hai. Yahi reason hai ki weak form ek loop ke andar belong karta hai — loop spurious failures ko free mein absorb kar leta hai.Kya std::atomic<double> fetch_add support karta hai?
fetch_add floating types ke liye provided nahi tha, isliye aapko ise ek CAS loop se emulate karna padta tha.Kya is_lock_free() kisi real platform par std::atomic<int> ke liye false return kar sakta hai?
Kya hoga agar compare_exchange_strong mein expected a ke barabar ho lekin a briefly kisi doosri value mein badla aur wapas aa gaya?
Aapko kya ordering milti hai agar aap a.load() ko bina kisi memory-order argument ke call karein?
memory_order_seq_cst milta hai — sabse strong, ek single global total order mein participate karta hai. Correct hai lekin potentially slower than acquire agar bas wahi aapko chahiye tha.Kya std::atomic<int> par ++a (member operator) a = a + 1 ke barabar hai?
++a ek single atomic fetch_add hai; a = a + 1 ek alag atomic load aur atomic store hai jiske beech ek race window hai. Operator ya fetch_add prefer karo.Recall Page band karne se pehle ek-line self-test
"Atomic" aur "ordered" ke beech ek asli difference batao. ::: Atomic = operation indivisible hai (koi torn/lost value nahi); ordered = uske aas-paas doosri memory writes kaisi visible hoti hain. Relaxed pehla rakhta hai, doosra drop kar deta hai.
Dekho bhi: Mutex and Lock · std::thread and std::async · Memory Model (C++) · Compare-And-Swap · False Sharing