Lesson 26 • Advanced
Mutexes, Locks & Deadlock Avoidance
Protect shared data with mutex, lock_guard, unique_lock, scoped_lock, and learn deadlock prevention strategies.
What You'll Learn
- ✓ mutex and lock_guard for thread safety
- ✓ unique_lock for flexible locking
- ✓ Deadlock causes and prevention
- ✓ scoped_lock for multi-mutex safety
What is a Mutex?
A mutex (mutual exclusion) is a lock that ensures only one thread can access a shared resource at a time. Think of it like a bathroom door lock — when one person locks it, everyone else waits until they're done.
| Lock Type | Flexibility | Use Case |
|---|---|---|
lock_guard | Lock once, auto-unlock | Simple critical sections |
unique_lock | Lock/unlock/try/defer | Condition vars, timed locks |
scoped_lock | Lock multiple mutexes | Deadlock-free multi-lock |
Data Races & lock_guard
See a data race in action and fix it with lock_guard
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
using namespace std;
// Data race WITHOUT mutex
void unsafeIncrement(int& counter, int iterations) {
for (int i = 0; i < iterations; i++) {
counter++; // NOT thread-safe!
}
}
// Thread-safe with lock_guard
class SafeCounter {
int count = 0;
mutex mtx;
public:
void increment(int iterations) {
for (int i = 0; i < iterations; i++) {
lock_guard<mutex> lock(mtx); // RAII lock
...unique_lock & Timed Locks
Use flexible locking with defer, try_lock, and timed_mutex
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
mutex mtx;
// unique_lock — more flexible than lock_guard
void flexibleLocking() {
// Deferred locking
unique_lock<mutex> lock(mtx, defer_lock);
cout << "Doing work without lock..." << endl;
this_thread::sleep_for(chrono::milliseconds(10));
// Lock when needed
lock.lock();
cout << "Critical section (locked)" << endl;
lock.unlock();
cout << "More work
...Deadlock Prevention
Avoid deadlocks with std::lock and scoped_lock
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mutexA, mutexB;
// DEADLOCK EXAMPLE (DO NOT USE!)
// void deadlockThread1() {
// lock_guard<mutex> lockA(mutexA);
// this_thread::sleep_for(chrono::milliseconds(10));
// lock_guard<mutex> lockB(mutexB); // Waits for B
// }
// void deadlockThread2() {
// lock_guard<mutex> lockB(mutexB);
// this_thread::sleep_for(chrono::milliseconds(10));
// lock_guard<mutex> lockA(mutexA); // Waits for A
...Common Mistakes
⚠️ Locking order: If thread 1 locks A then B, and thread 2 locks B then A — deadlock. Always use scoped_lock for multiple mutexes.
⚠️ Holding locks too long: Don't do I/O, network calls, or heavy computation while holding a lock. Lock only the critical section.
⚠️ Recursive locking: Locking the same mutex twice in one thread deadlocks. Use recursive_mutex if you must (but redesign first).
Pro Tips
💡 Minimise critical sections: Lock, do the minimum work, unlock. Prepare data outside the lock.
💡 Use scoped_lock (C++17): It replaces lock() + lock_guard(adopt_lock) with a single clean statement.
💡 Consider lock-free: For simple counters, std::atomic is faster than mutex. We'll cover this next.
📋 Quick Reference
| Operation | Syntax |
|---|---|
| RAII lock | lock_guard<mutex> lock(m); |
| Flexible lock | unique_lock<mutex> lock(m); |
| Multi-lock | scoped_lock lock(m1, m2); |
| Try lock | unique_lock lock(m, try_to_lock); |
| Deferred lock | unique_lock lock(m, defer_lock); |
Lesson Complete!
You now know how to protect shared data with mutexes and prevent deadlocks. Next: Atomic Operations — lock-free synchronisation for maximum performance.
Sign up for free to track which lessons you've completed and get learning reminders.