Question bank — std - condition_variable
5.2.25 · D5· Coding › C++ Programming › std - condition_variable
Shuru karne se pehle, teen simple English facts ek sticky note pe likh lo, kyunki almost har trap inhi mein se kisi ek pe aake girta hai:
Recall Teen facts jinpe har trap tika hai
Fact 1 — koi memory nahi: koi bhi notify jo tab bheja gaya jab koi wait nahi kar raha tha, woh simply gayab ho jaata hai. condition_variable use baad ke liye queue nahi karta. Fact 2 — flag hi sachchi baat hai: correctness shared predicate (shared state pe ek boolean) se aati hai, notification se nahi. Notify sirf ek "uth aur dobara check kar" wala dhakka hai. Fact 3 — ek hi mutex check aur change dono ko guard karta hai: waiter ka predicate-check aur notifier ka state-change dono ko usi ek mutex se serialize karna zaroori hai, warna ek wakeup crack se nikal sakta hai.
Pehle machinery ko picture karo
Traps se pehle, apni aankhon ko char tasveeron pe ground karo. Neeche har trap inhi diagrams mein se kisi ek ka galat hona hai — jab kisi question pe phans jao, wapas aao aur woh arrow point karo jo toot raha hai.
Figure 1 — sahi handshake. Yeh ek swim-lane timeline hai: baayein taraf Consumer lane, daayein taraf Producer lane, aur waqt neeche ki taraf behta hai. wait ke andar teen atomic steps dhyan se dekho: yeh mutex release karta hai (violet arrow lane se bahar jaata hai), sota hai (grey bar), aur lock sirf notify ke baad dobara leta hai (violet arrow wapas aata hai). Predicate do baar check hota hai — ek baar sone se pehle, ek baar jaagne ke baad.
Figure 2 — lost-wakeup race. Wohi do lanes, lekin producer flag flip karta hai aur notify karta hai lock pakde bina, woh chhoti si window (red band) mein land karta hai jo consumer ke false padhne ke baad lekin sone se pehle hai. Notify (orange) ek khaali kamre mein girta hai aur gayab ho jaata hai; consumer hamesha ke liye so jaata hai. Yahi "Spot the error" wala ready=true bug draw kiya gaya hai.
Figure 3 — predicate loop ka khulaasa. Ek akele waiter ki zindagi ek flowchart-timeline ke roop mein: har wakeup (spurious ya real) predicate dobara check karo pe wapas loop karta hai. Do dashed return-arrows follow karo — ek spurious wakeup ke liye, ek stolen item ke liye — dono tumhe "check pred" pe wapas dump karte hain, yeh sabit karta hai kyun while (not if) mandatory hai.
Figure 4 — notify_one vs notify_all. Left panel: teen soye hue consumers, notify_one sirf ek ko jagaata hai, baaki do soye rehte hain. Right panel: notify_all teeno ko jagaata hai — thundering herd — lekin sirf ek single item ko pakadta hai; baaki do dobara check karte hain, queue khaali paate hain (stolen wakeup), aur phir so jaate hain.
True ya false — justify karo
Neeche har claim aisa likha gaya hai jaise woh obviously sach ho. Decide karo, phir reasoning se justify karo — kabhi sirf haan/nahi mat kaho.
cv.notify_one() guarantee karta hai ki kam se kam ek thread eventually jagega aur aage badhega.
Ek condition_variable yaad rakhta hai kitne notifies mile taaki late waiter catch up kar sake.
notify_one() call karte waqt mutex pakdna zaroori hai.
cv.wait(lk, pred) use karna aur while(!pred()) cv.wait(lk); use karna equivalent hai.
Spurious wakeup ka matlab hai OS ne data kho diya aur program ab corrupt hai.
wait bina matching notify ke return karta hai. Loop simply predicate dobara check karta hai aur phir so jaata hai — koi corruption nahi, sirf ek extra check.Agar sirf ek producer aur ek consumer ho, toh while ki jagah if safe hai.
notify_all() hamesha notify_one() se safer hai, toh bas ise har jagah use karo.
notify_one() tab use karo jab ek akela waiter change handle kar sake; notify_all() tab use karo jab state (jaise done) sab ki concern ho.cv.wait(lk) soye waqt puri time mutex locked rakht hai.
wait atomically lock release karta hai aur so jaata hai, phir return karne se pehle ise dobara acquire karta hai (Figure 1). Agar soye waqt lock pakde rehta, toh koi notifier kabhi state change hi nahi kar paata — instant deadlock.std::lock_guard ko cv.wait mein pass karna theek hai jab tak tum manually unlock nahi karte.
wait ko khud mutex unlock aur relock karna hota hai, aur lock_guard koi unlock expose nahi karta. Yeh compile bhi nahi hoga; tumhe unique_lock chahiye.Error dhundho
Har snippet mein ek subtle bug hai. Use naam do aur batao kya toot jaata hai.
ready = true; cv.notify_one(); (flag set kiya bina lock pakde).
ready==false padhne aur wait call karne ke beech, notifier ready flip kar sakta hai aur shoonya mein notify kar sakta hai; waiter phir hamesha ke liye so jaata hai. Flag usi mutex ke andar set karo.std::unique_lock<std::mutex> lk(m); if (q.empty()) cv.wait(lk); process(q.front());
if (Figure 3). Spurious wakeup (ya notify_all ke andar stolen item) queue ko khaali chhod deta hai, aur q.front() khaali queue se padh leta hai — undefined behaviour. cv.wait(lk, []{ return !q.empty(); }); use karo.Producer: q.push(x); phir cv.notify_one(); — lekin q kisi bhi mutex se guard nahi hai.
std::queue pe concurrent push/pop undefined behaviour hai. Mutex ko har access protect karna chahiye, sirf wait ko nahi.Waiter mutex A lock karta hai; notifier flag ko ek alag mutex B ke andar change karta hai, phir notify karta hai.
cv.wait(lk, [&]{ return counter > 0; }); counter--; lekin counter ek plain int hai jo dusra thread lock ke bina likhta hai.
counter ko lock ke andar padhna lekin lock-free likhna ek data race hai; predicate inconsistent value observe kar sakta hai. Ya toh sab accesses mutex se guard karo ya atomic consistently use karo — mix mat karo.Consumer: lk.unlock(); cv.wait(lk); (unique_lock ko wait call karne se pehle manually unlock karta hai).
cv.wait require karta hai ki entry pe lock held ho taaki woh atomic unlock-and-sleep perform kar sake (Figure 1). Pehle se unlocked lock pass karna precondition violate karta hai.Why questions
::: answers yahan reasoning hi hai, koi fact yaad karne ke liye nahi.
wait ek raw boolean flag ki jagah mutex reference kyun leta hai?
Standard spurious wakeups allow kyun karta hai inhe forbid karne ki jagah?
wait ko sasta rakhta hai, aur mandatory predicate loop (Figure 3) inhe harmless bana deta hai waise bhi.notify_one() call karne se pehle unlock karna zyada prefer kyun kiya jaata hai?
Flag, notification nahi, truth ka source kyun ho sakta hai?
notify_all() "producer done ho gaya" signal ke liye notify_one() se better kyun fit hota hai?
done ek aisi fact hai jo har consumer ko observe karni chahiye exit karne ke liye; sirf ek ko jagaana baaki ko hamesha ke liye soye rehne deta hai (Figure 4). State jo sab waiters ki concern ho use notify_all chahiye.Busy-wait while(q.empty()){} loop cv.wait se CPU ke alawa aur bhi kyun bura hai?
wait 0% CPU pe sota hai aur producer ko yield karta hai.cv.wait(lk, pred) spurious wakeup ke baad bhi return karne se pehle lock dobara kyun acquire karta hai?
wait ke baad jo bhi code aaye woh shared state protected hone ke saath chale; caller hamesha usi "lock held, predicate true" state mein resume hota hai (Figure 1), reasoning uniform rehti hai.Edge cases
Woh scenarios jo happy path kabhi nahi dikhata.
Ek notify_one() fire hota hai aur abhi koi thread wait nahi kar raha — ek second baad wait karna shuru karne wale thread ka kya hota hai?
wait(lk, pred) turant true dekhta hai aur sota nahi; agar nahi, toh woh hamesha ke liye so jaata hai.Do consumers wait kar rahe hain, producer ek item push karta hai aur notify_all() call karta hai.
Predicate pehle se true hai thread ke kabhi wait karne se pehle (item consumer ke aane se pehle push ho gaya).
cv.wait(lk, pred) predicate pehle check karta hai; true paake, yeh bina soye aur bina kisi notify ki zaroorat ke turant return karta hai — flag ne akele hi state carry ki.Producer khatam ho jaata hai aur done set karta hai, lekin ek consumer mid-processing mein hai, wait nahi kar raha.
wait(lk, pred) pe wapas loop karta hai, toh predicate !q.empty() || done shared memory se done == true padhta hai aur woh cleanly exit kar jaata hai.Galti se, koi bhi producer kabhi state change karne se pehle ek notify_one() issue ho jaata hai.
notify_all() ke under saare waiting threads ek saath jagte hain aur sab apna predicate fail karte hain.
notify_one prefer karne ka jab sirf ek waiter hi change pe act kar sake.Ek one-shot "go" flag true set kiya jaata hai, consumers ko notify kiya jaata hai, phir ek brand-new thread join karta hai aur wait karta hai.
true rehta hai, newcomer ka predicate check turant pass ho jaata hai. Notification ke unlike, flag durable hai — yahi reason hai kyun level-triggered flag edge-triggered notify se behtar hai.