Concurrency — std - thread, std - mutex, std - lock_guard, std - unique_lock
WHY does concurrency exist at all?
WHAT are the four tools?
HOW to use them — built from first principles
Step 1: A data race (the disease)
int counter = 0;
void add() { for (int i = 0; i < 100000; ++i) ++counter; }
int main() {
std::thread t1(add), t2(add);
t1.join(); t2.join();
std::cout << counter; // NOT reliably 200000 — data race!
}Why this breaks: ++counter is really read → increment → write. Two threads interleave these steps, so increments get lost.
Step 2: The cure — protect with a mutex
int counter = 0;
std::mutex mtx;
void add() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lk(mtx); // locks here
++counter;
} // lk destroyed → unlock here, every iteration
}Why this works: at most one thread is inside the guarded region; ++counter becomes effectively atomic. Result is always 200000.

Worked examples
Common mistakes
Flashcards
What is a data race?
Why prefer lock_guard over manual lock()/unlock()?
unlock runs on every scope exit, including exceptions and early returns, preventing deadlock.What happens if a joinable std::thread is destroyed without join/detach?
std::terminate() is called, aborting the program.Difference between lock_guard and unique_lock?
lock_guard is minimal: locks at construction, unlocks at destruction, no early control. unique_lock is flexible: defer/early unlock/relock, movable, works with condition variables.Why does unique_lock (not lock_guard) pair with std::condition_variable?
cv.wait must unlock while sleeping and re-lock on wakeup, which only unique_lock allows.How do you lock two mutexes without risking deadlock?
std::defer_lock, then std::lock(a, b) (or std::scoped_lock(a,b) in C++17) to acquire all-or-nothing.Is std::thread copyable?
What does ++counter actually do at the hardware level?
Recall Feynman: explain it to a 12-year-old
Imagine two kids sharing one coloring book. If they both scribble at once, the picture turns to mush — that's a data race. So you make a rule: there's only one crayon (the mutex), and you must grab it before coloring and put it back after. lock_guard is a magic clip that automatically drops the crayon the moment you walk away, so you can never accidentally hog it. unique_lock is a fancier clip that also lets you put the crayon down early if you want a break. And a thread is just inviting a second kid to color with you — but you must say "okay, you're done now" (join) before cleaning up the table, or you'll knock everything over.
Connections
- RAII and resource management — lock_guard/unique_lock are RAII applied to locks
- std::atomic and lock-free programming — alternative to mutex for simple counters
- std::condition_variable — requires
unique_lock - Deadlock and lock ordering — why
std::lock/scoped_lockexist - std::async and std::future — higher-level task launching vs raw
std::thread - Undefined behavior in C++ — data races are formally UB
- Move semantics — why
std::threadis move-only
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, concurrency ka matlab hai ki tumhare program me ek se zyada threads ek saath chal rahe hain, kyunki aaj ke CPU me multiple cores hote hain. std::thread t(func) likhte hi naya thread launch ho jaata hai. Lekin yaad rakhna — thread object destroy hone se pehle tumhe t.join() (wait karo) ya t.detach() (chhod do) karna zaroori hai, warna program std::terminate se crash kar jaata hai.
Problem tab aati hai jab do threads ek hi shared variable ko chhute hain aur koi ek usse likhta (write) bhi hai. Jaise ++counter actually teen kaam hai — read, increment, write. Do threads beech me interleave ho jaate hain aur kuch increments lost ho jaate hain. Isko data race kehte hain, aur C++ me yeh undefined behavior hai — kuch bhi ho sakta hai.
Iska ilaaj hai std::mutex — ek aisi cheez jise ek time me sirf ek thread "lock" kar sakta hai. Baaki threads lock() pe ruk jaate hain jab tak woh free na ho. Par mutex ko haath se lock()/unlock() mat karo — agar beech me exception ya early return aa gaya to unlock chhoot jaayega aur permanent deadlock ho jaayega. Isliye hamesha std::lock_guard use karo: yeh constructor me lock karta hai aur scope khatam hote hi destructor me unlock — yeh kabhi skip nahi hota. RAII ka jaadu!
Aur jab zyada flexibility chahiye — jaise lock ko early release karna, ya condition_variable ke saath kaam karna — tab std::unique_lock use karo. Do mutex ek saath lene ho to std::defer_lock + std::lock(a,b) use karo taaki deadlock na ho. Simple rule: simple kaam me lock_guard, flexible kaam me unique_lock.