A strong answer
A std::mutex is the synchronization primitive that enforces mutual exclusion: only one thread may hold it at a time. You never call lock() and unlock() directly in modern code because an exception or early return would leave the mutex locked forever. Instead you use RAII wrappers.
std::lock_guard<std::mutex> is the simplest wrapper. Its constructor locks the mutex, and its destructor unlocks it. It has no flexibility — it locks on construction, unlocks on destruction, and cannot be moved or transferred. Use it when the critical section exactly matches a lexical scope (a function body, a block).
std::unique_lock<std::mutex> is the more capable wrapper. It can:
- Defer locking (
std::defer_lock) and lock later, which is required forstd::lockto acquire multiple mutexes without deadlock. - Release ownership early with
unlock()and relock later. - Transfer ownership via move semantics.
- Work with condition variables (
cv.wait(lk)requires aunique_lock).
Choose lock_guard for simple scopes and unique_lock when you need any of the above flexibility. Both are exception-safe: the mutex is always released when the wrapper goes out of scope.
A common pattern is protecting a data structure:
std::mutex mtx;
std::map<std::string, int> cache;
int get(const std::string& key) {
std::lock_guard<std::mutex> lk(mtx);
auto it = cache.find(key);
return it != cache.end() ? it->second : -1;
}The lock guards the entire lookup, so no other thread can modify the map while we read it. For more complex operations that need to check a condition and wait, unique_lock with a condition variable is the right tool.
What the interviewer is listening for
- Clear distinction between the mutex itself and the RAII wrappers.
- Understanding that
lock_guardis simpler and sufficient for most cases. - Knowing that
unique_lockis required forstd::lock, condition variables, deferred locking, and movable ownership. - Mentioning exception safety: destructors run on stack unwind, so the mutex is always released.
- Not confusing
unique_lockwithshared_lock(for read-write locks).
Common mistakes
- Calling
mtx.lock()andmtx.unlock()manually without RAII — a single exception leaks the lock. - Using
lock_guardwith a condition variable — it doesn't satisfycv.wait()'s requirements. - Forgetting that
lock_guardcannot be moved; putting it in a container or returning it from a function fails to compile. - Assuming
unique_lockhas zero overhead — it stores a pointer to the mutex and a state flag, but the cost is negligible compared to the mutex itself.