5.2.25 · D5 · HinglishC++ Programming

Question bankstd - condition_variable

2,273 words10 min read↑ Read in English

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.
False. Agar koi thread abhi wait nahi kar raha, toh notify lost ho jaata hai (Fact 1, Figure 2); ek thread jo baad mein wait karna shuru karta hai use kabhi nahi milta aur woh hamesha ke liye so sakta hai jab tak shared flag pehle se true na ho.
Ek condition_variable yaad rakhta hai kitne notifies mile taaki late waiter catch up kar sake.
False. Iske paas koi counter ya memory nahi hai. Yahi reason hai kyun hum ek real shared predicate test karte hain — flag survive karta hai, notification nahi.
notify_one() call karte waqt mutex pakdna zaroori hai.
False. Notify ke liye lock ki zaroorat nahi. Lekin tumne shared state ko lock ke andar zaroor change kiya hona chahiye tha; notify se pehle unlock karna ek legal optimization hai, requirement nahi.
cv.wait(lk, pred) use karna aur while(!pred()) cv.wait(lk); use karna equivalent hai.
True. Predicate overload usi exact loop mein expand hone ke liye defined hai (Figure 3), yahi reason hai kyun dono correctly spurious aur stolen wakeups se bach jaate hain.
Spurious wakeup ka matlab hai OS ne data kho diya aur program ab corrupt hai.
False. Spurious wakeup ek permitted, harmless event hai jahan 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.
False. Spurious wakeups thread count se independent hain; ek akela consumer bhi khaali queue ke saath jaag sakta hai. Loop (ya predicate overload) hamesha zaroori hai.
notify_all() hamesha notify_one() se safer hai, toh bas ise har jagah use karo.
Misleading, false maano. Yeh zyada baar correct hota hai, lekin bahut saare waiters ke saath yeh sab ko ek lock ke liye contend karne ke liye jagaata hai — ek "thundering herd" (Figure 4) jo CPU waste karta hai. 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.
False. 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.
False. 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).
Lost-wakeup race (Figure 2). Waiter ke 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());
Loop ki jagah 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.
Queue pe Data race. Bina lock ke 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.
Broken serialization (Fact 3). Waiter ka check aur notifier ka change alag locks se guard hain, isliye check-then-wait window unprotected hai — lost wakeups wapas aate hain. Dono sides ko ek mutex share karna chahiye.
cv.wait(lk, [&]{ return counter > 0; }); counter--; lekin counter ek plain int hai jo dusra thread lock ke bina likhta hai.
Torn/stale read. 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).
Undefined behaviour. 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?
Mutex hi "predicate check karo" aur "so jao" ko ek atomic step banata hai; iske bina, ek notify false check karne aur sone ke beech ki gap mein land kar sakta hai (lost-wakeup window, Figure 2).
Standard spurious wakeups allow kyun karta hai inhe forbid karne ki jagah?
Inhe forbid karna har OS/hardware combination pe expensive guarantees force kar deta; allow karna 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?
Agar tum lock pakde hue notify karo, toh jaaga hua thread turant lock karne ki koshish karta hai, pakda hua paata hai, aur phir se block ho jaata hai — ek waste wake-and-sleep context switch. Pehle unlock karne se woh pehli baar jaagne pe aage badh sakta hai.
Flag, notification nahi, truth ka source kyun ho sakta hai?
Kyunki notifications ephemeral hain (Fact 1) lekin flag shared memory mein rehta hai; late aane wala waiter flag dobara padh kar sahi answer paata hai chahe sab notifies bhej diye gaye hon.
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?
100% CPU jalane ke alawa, single core pe woh us producer ko hi starve kar sakta hai jo queue fill karta, isliye woh hamesha spin karta reh sakta 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?
Taaki 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?
Notify lost ho gaya tha (Fact 1), isliye newcomer poori tarah predicate pe depend karta hai. Agar flag lock ke andar set hua tha, toh uska 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.
Dono jagte hain aur lock ke liye race karte hain (Figure 4, right). Pehla predicate dobara check karta hai, item leta hai, aur queue khaali kar deta hai; doosra dobara check karta hai, khaali paata hai, aur phir so jaata hai. Yeh "stolen wakeup" exactly wahi reason hai kyun loop mandatory 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.
Koi problem nahi: consumer so nahi raha, isliye missed notify irrelevant hai. Jab woh 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.
Agar yeh bina waiter ke land kare, harmlessly lost ho jaata hai. Agar yeh kisi waiter ko jagaaye, woh waiter still-false predicate dobara check karta hai aur simply wapas so jaata hai (Figure 3) — predicate loop stray notify ko absorb kar leta hai.
notify_all() ke under saare waiting threads ek saath jagte hain aur sab apna predicate fail karte hain.
Har ek dobara lock karta hai, dobara check karta hai, false paata hai, aur wapas so jaata hai. Correctness bani rehti hai, lekin yeh wasted work hai — ek reason 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.
Kyunki flag shared memory mein 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.