Exercises — Concurrency — std - thread, std - mutex, std - lock_guard, std - unique_lock
5.2.24 · D4· Coding › C++ Programming › Concurrency — std - thread, std - mutex, std - lock_guard, s
Shuru karne se pehle, ek picture puri page pe use hone wali vocabulary fix karti hai — ek lock asliyat mein overlapping threads ke saath time mein kya karta hai.

Ise left se right tak time ki tarah padho. Do horizontal bars do threads hain. Amber block critical section hai (lock aur unlock ke beech ka code). Mutex jo rule enforce karta hai: amber blocks kabhi vertically overlap nahi karte — thread 2 wait karta hai (dashed) jab tak thread 1 release nahi kar deta.
Level 1 — Recognition
Goal: sahi tool ka naam batao. Abhi koi code likhna nahi.
Recall Solution 1.1
std::lock_guard<std::mutex>. Yeh minimal RAII lock hai: apne constructor mein lock karta hai, destructor mein unlock karta hai, aur koi doosra operation nahi deta. "Sabse simple tool jo fit ho" → lock_guard.
Recall Solution 1.2
std::unique_lock<std::mutex> zaroori hai. std::condition_variable::wait ko sote waqt unlock aur wakeup pe re-lock karna hota hai; sirf unique_lock hi unlock()/lock() expose karta hai. lock_guard apne destructor se pehle unlock nahi kar sakta, isliye woh participate nahi kar sakta.
Recall Solution 1.3
t.join()— current thread ko block karo jab taktkhatam na ho jaye, phir uske resources release karo.t.detach()—tko apne aap chhalne do; tum uska wait karne ki ability kho dete ho, aur woh khatam hone pe khud clean up kar leta hai. Destruction se pehle ek joinable thread pe exactly ek call karni hi hogi.
Level 2 — Application
Goal: sahi, compiling code likho.
Recall Solution 2.1
int counter = 0;
std::mutex mtx;
void add(int N) {
for (int i = 0; i < N; ++i) {
std::lock_guard<std::mutex> lk(mtx); // is iteration ko lock karo
++counter; // read-modify-write, ab serialized hai
} // lk destroy hoga -> unlock
}Do threads, har ek N increments karte hue, har ++counter mutex ke andar → koi lost updates nahi. Guaranteed final value .
Recall Solution 2.2
std::mutex io_mtx;
void say(const std::string& s) {
std::lock_guard<std::mutex> lk(io_mtx);
std::cout << s << "\n";
}std::cout ek shared object hai; lock ke bina do threads ke characters interleave ho jaate hain. Lock poore (chote) scope ko cover karta hai aur kabhi early unlock nahi karta → lock_guard sahi, sabse sasta choice hai.
Recall Solution 2.3
std::thread move-only hai — woh ek unique OS thread own karta hai, isliye copying delete hai. push_back(t) copy karne ki koshish karta hai → compile error. Fix: ise move karo.
pool.push_back(std::move(t)); // ownership transfer; t ab empty (non-joinable) haiMove semantics dekho ki kyun ek move t ko valid lekin resource-less state mein chhod deta hai.
Level 3 — Analysis
Goal: bug dhoondho, outcomes gino, behavior ke baare mein reason karo.
Recall Solution 3.1
++counter = ek register mein counter read karo → register increment karo → write back karo.
Woh interleaving jo ek update lose karta hai (start counter = 41):
| time | Thread A | Thread B | counter |
|---|---|---|---|
| 1 | read 41 | 41 | |
| 2 | read 41 | 41 | |
| 3 | inc → 42 | 41 | |
| 4 | inc → 42 | 41 | |
| 5 | write 42 | 42 | |
| 6 | write 42 | 42 |
Dono threads ne increment kiya, lekin final value 42, 43 nahi hai — ek increment lost ho gaya. Yeh unsynchronized shared write ek data race hai → Undefined behavior in C++.
Recall Solution 3.2
std::mutex mtx;
void worker() {
std::unique_lock<std::mutex> lk(mtx); // locked
int x = read_shared(); // lock ke under shared read
lk.unlock(); // EARLY release
heavy(x); // koi shared state nahi -> koi lock nahi chahiye
}lock_guard scope end se pehle unlock nahi kar sakta, isliye woh heavy ke poore time lock hold karega — bina kisi wajah ke doosron ko block karta hua. unique_lock unlock() early kar sakta hai, isliye yeh mandatory hai. Flexible tool ko sirf tabhi use karo jab tum flexibility use kar rahe ho.
Recall Solution 3.3
Naive body: lock(a.m); lock(b.m); .... Phir:
- Thread 1
alock karta hai,bchahta hai. - Thread 2
block karta hai,achahta hai. - Har ek woh hold karta hai jo doosre ko chahiye → circular wait = deadlock. Koi aage nahi badhta.

Yeh ek timing bug hai kyunki yeh tabhi trigger hota hai jab dono threads apna pehla lock grab kar lein kisi ke bhi doosra grab karne se pehle. Agar thread 1 poori tarah khatam ho jaye thread 2 ke shuru hone se pehle, koi deadlock nahi — program "kaam karta hai" aur bug chhup jaata hai. Yahi intermittency hai jo lock-ordering bugs ko dangerous banati hai. Deadlock and lock ordering dekho.
Level 4 — Synthesis
Goal: ek real design solve karne ke liye tools combine karo.
Recall Solution 4.1
void transfer(Account& a, Account& b, int amt) {
std::unique_lock<std::mutex> la(a.m, std::defer_lock); // construct, lock MAT karo
std::unique_lock<std::mutex> lb(b.m, std::defer_lock);
std::lock(la, lb); // DONO ko atomically lock karo, deadlock-free
a.bal -= amt;
b.bal += amt;
} // dono RAII se release ho jaate hainstd::defer_lock har unique_lock ko adopt karta hai lekin lock nahi karta apna mutex. std::lock(la, lb) ek back-off algorithm use karta hai: woh sab grab karne ki koshish karta hai, aur agar ek nahi mila toh jo bhi hold kar raha hai use release karke retry karta hai, isliye koi thread ek mutex hold karte hue doosre ka wait nahi karta → circular-wait condition toot jaati hai. (C++17: std::scoped_lock lk(a.m, b.m); ek line mein yahi karta hai.)
Recall Solution 4.2
std::mutex m;
std::condition_variable cv;
std::queue<int> q;
int consume() {
std::unique_lock<std::mutex> lk(m); // unique_lock: cv ko unlock/relock chahiye
cv.wait(lk, []{ return !q.empty(); }); // predicate spurious wakeups se bachata hai
int v = q.front(); q.pop();
return v;
} // lk release ho jaata haiunique_lockisliye kyunkicv.waitsote waqtlkunlock karta hai aur wakeup pe re-lock karta hai —lock_guardnahi kar sakta.- Predicate form
wait(lk, pred)har wakeup pe!q.empty()dobara check karta hai, taaki ek spurious wakeup (OS bina real notify ke thread jagata hai) empty queue ko pop na kare. std::condition_variable dekho.
Level 5 — Mastery
Goal: tradeoffs ke saath design karo; limits aur alternatives ke baare mein reason karo.
Recall Solution 5.1
Dono sahi final value dete hain .
- (a) mutex: har
++counterlock + unlock pay karta hai (threads collide hone par kernel-mediated contention). Sahi hai lekin ek one-instruction critical section ke liye heavy hai. - (b)
std::atomic<long>:counter.fetch_add(1)(ya++counter) ek single lock-free hardware instruction hai; koi blocking nahi, koi OS involvement nahi. Ek pure counter ke liye,std::atomicfaster hai — mutexes tab shine karte hain jab critical section ek trivial op se zyada ho. std::atomic and lock-free programming dekho.
Recall Solution 5.2
template <class T>
class Guarded {
T data;
std::mutex m;
public:
template <class F>
auto with(F&& f) { // data tak pahunchne ka AKEELA rasta
std::lock_guard<std::mutex> lk(m); // poori call ke liye lock
return f(data); // callback ko reference do
} // auto-unlock
};
// usage: g.with([](int& x){ ++x; });data private hai; uska ekeela rasta with hai, jo pehle lock karta hai. Koi API nahi jo ek raw reference de jo lock se zyada jiye, isliye "bina locking ke use karna" compile hi nahi hoga. Yeh RAII and resource management hai apni limit tak push kiya: type system locking discipline enforce karta hai.
Recall Solution 5.3
std::async/std::future tab use karo jab thread ka kaam ek value compute karke return karna ho. future::get() result bina kisi shared mutable state aur bina manual mutex ke transfer karta hai — tumhe value aur koi bhi exception milti hai, tumhare liye synchronized. Yeh data-race bugs ki ek poori class hata deta hai.
Yahan f.get() return karta hai. std::async and std::future dekho.
Recall Ladder ki ek-line recap
L1 tool ka naam ::: lock_guard simple, unique_lock flexible, threads pe join/detach.
L2 likho ::: read-modify-write guard karo; threads move-only hain.
L3 analyze karo ::: ++ read-inc-write hai; naive two-lock ordering timing se deadlock karta hai.
L4 synthesize karo ::: std::lock(defer,defer) deadlock ko harata hai; wait karne ke liye unique_lock+cv.
L5 master karo ::: counters ke liye atomic, results ke liye future, Guarded<T> locking mandatory banata hai.
Connections
- Parent topic (Hinglish)
- RAII and resource management — yahan har lock wrapper ke peeche ka mechanism
- std::atomic and lock-free programming — Exercise 5.1 ka faster counter
- std::condition_variable — Exercise 4.2 ka bina-spinning wait
- Deadlock and lock ordering — Exercises 3.3 & 4.1
- std::async and std::future — Exercise 5.3
- Undefined behavior in C++ — data race asliyat mein kya hota hai
- Move semantics —
std::threadmove-only kyun hai