TRT
Sign in
mediumAdvancedconcept

How do you protect shared data with mutex and lock guards?

Explain the difference between std::mutex, lock_guard, and unique_lock, and when to use each for thread-safe data access.

TRT practice prompt — not a verified question from a named employer.

The Runtime Theory Team2 min read
Learn it firstMutual Exclusion: mutex, lock_guard and unique_lockPro — members onlyProtect shared counters and caches with mutex ownership, compare lock_guard with unique_lock, and scale reads with shared_mutex.

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 for std::lock to 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 a unique_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:

cpp
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_guard is simpler and sufficient for most cases.
  • Knowing that unique_lock is required for std::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_lock with shared_lock (for read-write locks).

Common mistakes

  • Calling mtx.lock() and mtx.unlock() manually without RAII — a single exception leaks the lock.
  • Using lock_guard with a condition variable — it doesn't satisfy cv.wait()'s requirements.
  • Forgetting that lock_guard cannot be moved; putting it in a container or returning it from a function fails to compile.
  • Assuming unique_lock has zero overhead — it stores a pointer to the mutex and a state flag, but the cost is negligible compared to the mutex itself.

This answer walks

Practice follow-ups

  1. 01When would you prefer unique_lock over lock_guard?
  2. 02What happens if a mutex is destroyed while still locked?

Not started

Sign in to save your learning progress.

Sign in to save