Worked examples — Concurrency — std - thread, std - mutex, std - lock_guard, std - unique_lock
5.2.24 · D3· Coding › C++ Programming › Concurrency — std - thread, std - mutex, std - lock_guard, s
Scenario matrix
Koi bhi code likhne se pehle, har case class ka naam rakh lete hain taaki prove kar sakein ki koi miss nahi hua. Socho jaise circle draw karne se pehle har quadrant list karna: agar ek quadrant skip kiya, toh ek din koi reader wahan pahunch kar kho jaayega.
| Cell | Situation | Danger / Question | Covered by |
|---|---|---|---|
| A | Unprotected shared counter | Data race → galat total | Ex 1 |
| B | Same counter, mutex add kiya | Kya total bilkul exact ho jaata hai? | Ex 1 |
| C | Shared std::cout (I/O object) |
Characters interleave ho jaate hain | Ex 2 |
| D | Lock slow work ke dauran held | Doosron ki needless blocking | Ex 3 |
| E | Do mutexes, opposite orders | Deadlock (circular wait) | Ex 4 |
| F | Wait-for-a-condition | lock_guard sleep-unlock nahi kar sakta |
Ex 5 |
| G | Degenerate: zero iterations / empty work | Kuch hota bhi hai? | Ex 6 |
| H | Degenerate: ek thread dwara same mutex twice lock | Self-deadlock (UB) | Ex 7 |
| I | Limiting case: N threads, N badhta hua | Kya correctness scale ke saath survive karti hai? | Ex 8 |
| J | Word problem: bank / ticket counter | Ek real story ko locks mein translate karo | Ex 4, Ex 9 |
| K | Exam twist: thread / unique_lock ko move karna |
Ownership transfer, no copy | Ex 10 |
Neeche har numeric claim machine-checked hai verify block mein. Concurrency logic (jo C++ mein timing-dependent hoti hai) deterministic models se check ki gayi hai — hum Python mein interleaving simulate karte hain taaki reasoning provable ho, "trust me" wali nahi.
Example 1 — Cells A & B: race aur uska ilaaj
Step 1 — ++counter ko decompose karo.
Yeh step kyun? Kyunki "atom" poora statement nahi hai. Hardware level par ++counter read → add 1 → write back hai: teen alag steps.
Step 2 — Max value. Kyun? Best case yeh hai ki do threads kabhi interleave nahi karte — ek poora hone ke baad doosra chalta hai. Tab har increment count hoti hai: .
Step 3 — Min value (worst interleave). Kyun? Agar dono threads ek hi purani value read karein, dono same nayi value compute karein, aur dono usse write karein — ek increment destroy ho jaati hai. Pathological extreme mein, ek thread apne saare 100000 increments karta hai jabki doosra apni har write se pehle ek stale value read karta hai; sabse chhota guaranteed value sirf hai (thread A ke 100000 plus kam se kam ek surviving write). Isliye ek data race undefined behavior hai — standard koi promise nahi karta, isliye hum sirf isse bound karte hain.
Step 4 — lock_guard ke saath.
Kyun? Guard ek waqt mein sirf ek thread ko region ke andar allow karta hai, isliye read-add-write effectively atomic ho jaata hai. Koi increment kabhi lost nahi hoti: exactly .
Verify: ✓ (neeche check kiya gaya). Racy lower bound aur upper bound inequality ke roop mein check kiye gaye hain.
Example 2 — Cell C: shared std::cout
Step 1 — Total characters count karo. Kyun? Locking kabhi data add ya drop nahi karta; sirf order karta hai. characters dono cases mein aate hain.
Step 2 — Lock ke bina intact blocks. Kyun? Interleaving kisi bhi thread ke output ko split kar sakta hai. Guaranteed intact blocks (worst case).
Step 3 — lock_guard<mutex> ke saath intact blocks, poore print ke around.
Kyun? Har thread apne poore 5-char write ke liye lock hold karta hai, isliye koi doosra thread beech mein character nahi daal sakta. Saare blocks intact rehte hain.
Verify: total; intact-with-lock ; intact-without (worst) ✓.
Example 3 — Cell D: lock early release karna
Step 1 — Case (a): sab kuch ke liye lock held. Kyun? Kyunki lock read (1) + math (100) = 101 units cover karta hai, aur sirf ek worker ek baar andar ho sakta hai, serialized total units hai.
Step 2 — Case (b): read ke baad unlock. Kyun? Ab sirf 1-unit read serialized hai; 100-unit math lock ke baahir parallel mein chalta hai. Serialized lock time units.
Step 3 — Moral.
unique_lock kyun aur lock_guard kyun nahi? Sirf unique_lock hi unlock() early kar sakta hai. Flexibility tabhi use karo jab faayda ho — yahan serialized time mein 101× ki kami.
Verify: , ✓. Work overlap karne ke doosre tareekon ke liye std::condition_variable aur std::async and std::future dekho.
Example 4 — Cells E & J: do mutexes, deadlock, aur fix

Step 1 — Naive circular wait ko model karo. Kyun? Thread 1 A hold karta hai aur B chahta hai; thread 2 B hold karta hai aur A chahta hai. Koi aage nahi badh sakta → "waits-for" graph mein ek cycle. Figure mein red arrows ko dekho jo ek loop bana rahe hain — woh loop hi deadlock hai.
Step 2 — Worst interleave ke under completions count karo. Kyun? Deadlocking schedule ke under, transfers mein se complete hote hain.
Step 3 — Fix: defer_lock + std::lock.
Kyun? std::lock(la, lb) back-off use karke all-or-nothing acquire karta hai: agar dono nahi mil sakte, toh release karke retry karta hai, isliye koi thread kabhi ek hold karte hue doosre ka wait nahi karta. Cycle kabhi form nahi ho sakti. Dono transfers complete hote hain: of .
Step 4 — Paise ka sanity check. Kyun? Locking kabhi arithmetic nahi badlata, sirf timing badlata hai. Agar A 100 se shuru hota hai, B 100 se, aur hum 30 A→B aur 40 B→A transfer karein, toh final A , final B . Total conserved: .
Verify: naive completions , safe completions ; final balances , , total ✓.
Example 5 — Cell F: condition ka wait karna
Step 1 — cv.wait(lk, pred) kya karta hai.
Yeh tool kyun? Ek busy-loop while(empty()){} poora CPU core jalata hai. std::condition_variable thread ko sleep karne aur sirf notify hone par jagane deta hai.
Step 2 — Ise unique_lock ki zaroorat kyun.
Kyun? wait ko karna hai: sone se pehle mutex unlock (taaki producer item add kar sake), phir wakeup par re-lock. lock_guard unlock-then-relock nahi kar sakta; sirf unique_lock woh expose karta hai. Yeh ek wakeup per ek unlock + ek relock = 2 lock-state changes hain.
Step 3 — Items consumed. Kyun? Genuine wakeup ke baad predicate true hai (queue mein ≥1 item hai); consumer exactly pop karta hai.
Step 4 — Spurious wakeups.
Predicate form wait(lk, pred) kyun? Ek thread bina item ke jaag sakta hai (ek "spurious wakeup"). Predicate re-check karta hai aur waapis so jaata hai, false wakeup par items consume karta hai. Predicate hamesha pass karo.
Verify: lock-state changes per wakeup ; true wakeup par items ; spurious wakeup par ✓.
Example 6 — Cell G: degenerate empty case
Step 1 — Zero iterations wala loop.
Kyun? Condition 0 < 0 immediately false hai, isliye body kabhi run nahi hoti. lock_guard body ke andar hai → woh kabhi construct nahi hota.
Step 2 — Counter aur lock counts.
Kyun? Koi body nahi ⇒ koi increment nahi ⇒ counter par rehta hai; mutex baar lock hota hai, saare threads mein.
Verify: final counter ; total locks ✓.
Example 7 — Cell H: same mutex ko twice lock karna
Step 1 — Non-recursive mutex kya promise karta hai.
Kyun? std::mutex recursive nahi hai: same thread jo pehle se mutex hold kar raha hai, usse dobara lock karna undefined behavior hai — practically yeh self-deadlock karta hai.
Step 2 — Blocked threads count karo.
Kyun? Thread ek lock ka wait karta hai jo sirf wahi release kar sakta hai, lekin woh wait mein stuck hai — isliye woh kabhi unlock tak nahi pahunch sakta. Exactly thread permanently blocked hai, aur kyunki woh mutex hold karta hai, koi bhi doosra thread jo ise chahta hai woh bhi block ho jaata hai.
Step 3 — Fix.
Kyun? Agar aapko sach mein re-entrancy chahiye, std::recursive_mutex use karo; aam taur par real fix restructuring hai taaki ek hi baar lock karo. Yahan std::atomic par kabhi rely mat karo — ek normal mutex ka plain re-lock simply UB hai. Lock-free alternatives ke liye std::atomic and lock-free programming compare karo.
Verify: self-deadlocked threads ✓.
Example 8 — Cell I: N threads tak scale karna
Step 1 — Formula. Kyun? Har increment serialized hai aur koi lost nahi hota, isliye total simply har increment ka sum hai: .
Step 2 — Evaluate karo. Kyun? .
Step 3 — Correctness -independent kyun hai. Kyun? Mutex mutual exclusion guarantee karta hai chahe kitne bhi threads compete karein — threads add karna speed aur contention badlata hai, final value kabhi nahi. (Ek mutex par zyaada threads aapko slow kar sakte hain; pure counters ke liye std::atomic and lock-free programming prefer karo.)
Verify: ✓.
Example 9 — Cell J: ticket counter (real-world)
Step 1 — Lock kyun zaroori hai.
Kyun? Iske bina, do threads dono tickets == 1 read kar sakte hain, dono decrement kar sakte hain, aur tickets bech sakte hain jo exist hi nahi karti (overselling — ek classic race). Lock check-and-decrement ko ek atomic decision banata hai.
Step 2 — Outcomes count karo. Kyun? Sirf 3 tickets exist hain. Lock ke saath, exactly customers succeed karte hain, bacha rehta hai, aur door bheje jaate hain. Koi overselling possible nahi.
Step 3 — Conservation check. Kyun? Sold + remaining starting stock ke barabar hona chahiye: ✓, aur sabka hisaab hai: .
Verify: sold , remaining , turned away ; conservation aur ✓.
Example 10 — Cell K: ownership transfer (exam twist)
Step 1 — Threads move-only hote hain.
Kyun? Ek std::thread exactly ek OS thread ka owner hota hai — ek unique resource. Move semantics woh ownership transfer karta hai; ise copy karna do owners of one thread imply karta, jo forbidden hai.
Step 2 — Move ke baad state.
Kyun? std::move(t1) OS thread t2 ko hand karta hai. Ab t2 joinable hai aur t1 empty hai (not joinable). Existence mein OS threads .
Step 3 — Copy line.
Kyun? Copy constructor deleted hai → std::thread t2 = t1; compile nahi hota (0 successful copies).
Verify: joinable-t1 (false), OS threads , copy compiles (false) ✓.
Recall Rapid recall
Do 100000 loops ka worst-case racy total ::: kam se kam 100001, zyaada se zyaada 200000; safe = exactly 200000 4 workers ke liye lock-held time, read 1 + math 100, early unlock ke saath ::: 4 units (vs 404 poora hold karte hue) Naive A-then-B / B-then-A transfers jo deadlock ke under complete hote hain ::: 0 of 2; std::lock ke saath, 2 of 2 Ek cv.wait wakeup ke dauran lock-state changes ::: 2 (sone ke liye unlock, wake par relock) 5 threads par zero-iteration loop ke liye final counter ::: 0, mutex 0 baar lock hua Stock 3 se 10 buyers ke saath lock ke under tickets biki ::: 3 biki, 0 baaki, 7 door bheje gaye Ek std::thread copy karna ::: compile nahi hota — move-only
Connections
- Parent topic
- RAII and resource management — kyun guard ka destructor har example ka hero hai
- Deadlock and lock ordering — Example 4 ka circular wait, depth mein
- std::condition_variable — Example 5 ki wait/notify machinery
- std::atomic and lock-free programming — Examples 1 & 8 ke liye lock-free ilaaj
- std::async and std::future — Example 3 jaisa kaam overlap karna manual threads ke bina
- Move semantics — Example 10 ka ownership transfer
- Undefined behavior in C++ — Examples 1 & 7 ka formal status