5.2.27 · D5 · HinglishC++ Programming

Question bankMemory model — happens-before, acquire-release semantics

1,958 words9 min read↑ Read in English

5.2.27 · D5 · Coding › C++ Programming › Memory model — happens-before, acquire-release semantics

Shuru karne se pehle, ek do-word glossary taaki neeche ke har trap pehli line se padhne mein aaye:


True or false — justify

Ek flag ko std::atomic<bool> banana automatically us plain int data ko protect karta hai jo tumne usse pehle likha tha.
False. Atomic sirf flag ki torn reads rokta hai; plain data ki ordering sirf happens-before edge se hoti hai, jiske liye release/acquire chahiye, sirf atomicity nahi. Dekho std-atomic and atomic operations.
Store aur load dono par memory_order_relaxed phir bhi atomic variable par koi data race nahi hone ki guarantee deta hai.
Atomic ke liye True — relaxed phir bhi atomic hai, isliye atomic kabhi tear nahi hoga — lekin surrounding non-atomic data ke liye false, jise koi ordering nahi milti aur woh race karta hai.
Ek acquire-load un memory operations ko order karta hai jo program order mein usse pehle aati hain.
False. Acquire ek one-way upward fence hai: yeh baad ki operations ko uske upar jaane se rokta hai. Pehle waali ops uski concern nahi hain.
Ek release-store un operations ko order karta hai jo program order mein uske baad aati hain.
False. Release ek one-way downward fence hai: yeh pehle ki ops ko uske upar pin karta hai; baad wali ops phir bhi uske upar float kar sakti hain.
acq_rel har thread ko saari atomic operations ka ek single, globally consistent order deta hai.
False. acq_rel sirf memory ko ek specific synchronizes-with chain ke saath order karta hai. Global total order ke liye seq_cst chahiye; dekho Sequential consistency vs weak memory models.
Agar Thread 2 ka acquire-load Thread 1 ke release-store se purani value padhta hai, toh bhi woh dono synchronize-with karte hain ek doosre ke saath.
False. Yeh edge sirf tab exist karta hai jab acquire actually woh released value (ya uski release sequence mein baad ki value) padhta hai. Ek purani value padhna matlab handshake kabhi complete nahi hua.
Happens-before transitive hota hai.
True. Agar aur toh — woh transitivity exactly yahi kaam karti hai ki ek release/acquire pair usse pehle sequenced saari cheezein saath le jaata hai.
Do threads ek hi non-atomic variable mein likhte hue, agar unke beech happens-before edge ho, toh woh data race hai.
False. Ek happens-before edge dono accesses ko order karta hai, isliye yeh race nahi hai. Data races ke liye ordering ki abscence chahiye.
Mutex lock karna acquire ki tarah behave karta hai, aur unlock karna release ki tarah.
True. Lock acquire ki tarah kaam karta hai, unlock release ki tarah, isliye critical sections apni writes us agli thread ko publish karti hain jo lock karti hai.
seq_cst jab bhi doubt ho, hamesha sahi default choice hai.
Spirit mein True — yeh sabse safe hai aur language default bhi hai — lekin yeh often slower hota hai, global total order ke liye extra fences ki keemat chukani padti hai. Pehle correctness, phir deliberately relax karo.
Compiler reordering aur CPU reordering ek hi phenomenon ke do naam hain.
False. Yeh alag layers hain: compiler as-if rule ke under build time par reorder karta hai; CPU runtime par out-of-order execution aur store buffers ke zariye reorder karta hai. Ek memory tag ko dono ko defeat karna padta hai.
Ek single thread ke andar, reordering us usi thread ke zariye kabhi bhi observe ki ja sakti hai.
False. As-if rule guarantee karta hai ki single-thread execution program order mein dikhti hai; reordering sirf across threads observable hoti hai.

Spot the error

Ek programmer data = 42; likhta hai phir ready.store(true, relaxed); aur expect karta hai ki consumer data == 42 dekhega.
Error: relaxed koi synchronizes-with edge nahi banata, isliye data = 42 aur consumer ke read ke beech koi happens-before nahi hai — assert fire ho sakta hai. release/acquire use karo.
Ek consumer flag padhne ke liye ready.load(release) karta hai.
Error: release ek store ordering hai; load release nahi ho sakta. Load ko acquire (ya relaxed/seq_cst) use karna chahiye. Compiler reject karega ya semantics meaningless hongi.
Koi fetch_add read-modify-write par acquire use karta hai aur expect karta hai ki unki pehle ki writes publish ho jaayein.
Error: ek RMW jo pehle ki writes publish bhi kare aur doosron ki writes consume bhi kare, usse acq_rel chahiye. Pure acquire koi release side nahi deta, isliye pehle ki writes uske upar pin nahi hoti.
Ek team do threads ke beech time gap par rely karti hai ("Thread 1 pehle chala, isliye uski writes zaroor visible hain") acquire/release skip karne ke liye.
Error: synchronization data flow ke baare mein hai, wall-clock time ke baare mein nahi. Bina value ke writer se reader tak ek atomic ke zariye travel kiye, chahe "pehle koi bhi chala ho" — koi ordering nahi hogi.
Code reference-count decrement ke liye relaxed use karta hai, phir count zero hote hi bina kisi fence ke object delete kar deta hai.
Error: final destructor ko saare prior threads ki writes dikhni chahiye, jiske liye zero pahunchne wale decrement par acquire (ya acq_rel) chahiye, warna deletion un writes ke saath race karega.
Ek busy-wait loop while(!ready.load(relaxed)); use ki jaati hai, phir guarded data padha jaata hai.
Error: loop sahi se terminate hota hai (relaxed phir bhi modification order update karta hai) lekin koi hb edge nahi banata, isliye baad wala data read stale ho sakta hai. Load acquire hona chahiye.

Why questions

Memory model acquire-load se kyun demand karti hai ki woh exact stored value padhe, koi bhi value nahi?
Kyunki ordering data dependency se carry hoti hai: sirf tab jab published value physically writer se reader tak travel karti hai, reader writer ke prior work ko inherit kar sakta hai. Alag value matlab usne alag (ya koi nahi) publication observe ki.
seq_cst strictly acq_rel se stronger kyun hai jabki dono acquire aur release include karte hain?
acq_rel sirf individual synchronizes-with chains ke saath order karta hai; seq_cst additionally ek global total order force karta hai jis par saare seq_cst ops agree karte hain, woh IRIW/Dekker inconsistency defeat karte hue jo acq_rel permit karta hai.
Do independent reader threads do independent acq_rel flag writes ke order par disagree kyun kar sakte hain?
Kyunki acq_rel koi cross-flag global order nahi deta — har reader dono stores ko caches se alag sequence mein propagate hote dekh sakta hai. Sirf seq_cst iska mana karta hai (woh IRIW case).
Data race pure galat value produce karne ki bajaye poore program ko undefined behavior kyun banata hai?
Standard racing accesses ko UB define karta hai taaki compilers assume kar sakein ki woh kabhi nahi hote aur freely optimize kar sakein; ek baar assumed-impossible behavior occur ho jaaye, koi bhi outcome (crash, silent corruption) permitted hai.
Ek plain bool ready = true flag kyun insufficient hai — chahe aaj CPU strongly ordered ho?
Compiler phir bhi as-if rule ke under plain write ko reorder ya cache kar sakta hai, aur portable code ko weak-memory CPUs par survive karna hai. Correctness model se aani chahiye, lucky platform se nahi.
Release "downward" act kyun karta hai aur acquire "upward" — dono directions kyun nahi?
Ek publisher ko sirf apna earlier kaam signal ke neeche pin karna hota hai (downward); ek subscriber ko sirf apne later reads signal dekhne ke baad pin karne hote hain (upward). One-way fences saste hain aur message passing ke liye exactly sufficient hain.
relaxed koi ordering na dene ke bawajood useful kyun ho sakta hai?
Counters aur statistics ke liye jahan sirf atomicity mayne rakhti ho (surrounding data order nahi karna), relaxed fence cost avoid karta hai — yeh Lock-free programming patterns ka ek staple hai.

Edge cases

Agar acquire-load kabhi released flag observe nahi karta (hamesha spin karta rahe), toh kya koi happens-before edge exist karta hai?
Nahi. Stored value ki koi successful read nahi, koi synchronizes-with edge nahi banta, isliye kuch bhi publish nahi hota — reader kuch bhi nahi dekhega chahe baad mein ruk bhi jaaye.
Jab dono operations relaxed ke saath ek hi thread par hon toh kya happens-before guarantees exist karti hain?
Poori sequenced-before ordering intra-thread phir bhi hold karti hai — relaxed kabhi bhi single-thread program order ko weak nahi karta; weakness purely cross-thread hai.
Kya alag threads se ek hi atomic par do relaxed stores ke beech koi ordering hai?
Haan — har atomic ka ek single modification order hota hai, isliye saare threads us ek variable par writes ki sequence par agree karte hain, relaxed ke under bhi. Jo unhe nahi milta woh hai surrounding memory ki ordering.
Kya koi matching release fence ke bina ek acquire fence kuch bhi useful karta hai?
Apne aap, koi synchronizes-with edge nahi banta; ek standalone fence sirf tab help karta hai jab doosri side par ek release ho aur ek atomic load/store unhe link kare.
Agar data khud std::atomic ho aur dono sides par relaxed se read/write ho, toh kya message-passing example phir bhi broken hai?
Data ab race nahi karta (woh atomic hai), lekin consumer phir bhi stale data value padh sakta hai kyunki relaxed flag ke saath koi ordering nahi deta — logically galat, though UB nahi.
Thread spawn karne wale thread ke respect mein naye thread ke pehle operation par ordering guarantee kya hai?
Thread creation ek happens-before edge establish karta hai: std::thread construction se pehle sequenced har cheez naye thread ke pehle operation se happens-before hai, isliye child parent ki prior writes dekhta hai.

Recall Har trap ka one-line summary

Ordering sahi tag ke zariye data flow se aati hai (release publish karta hai, acquire subscribe karta hai, value travel karni chahiye), kabhi time se nahi, kabhi "atomic ≈ safe" se nahi, aur acq_rel local hai jabki sirf seq_cst global hai.