ProgrammingC++ Multithreaded/Server Developer

What nuances should be considered when working with multithreading in C++? Describe the difference between std::mutex, std::lock_guard, std::unique_lock and provide an example of safe interaction with threads.

Pass interviews with Hintsage AI assistant

Answer

In modern C++ (starting from C++11), there is a rich toolkit for working with multithreading: std::thread, mutexes, and various wrapper objects for managing resource access. Safe interaction between threads requires understanding the following components:

  • std::mutex — the main locking primitive for synchronizing access. Requires explicit lock/unlock.
  • std::lock_guard — a RAII wrapper over the mutex that automatically locks and unlocks the mutex upon entering/exiting the scope.
  • std::unique_lock — a more versatile RAII wrapper that allows manual locking/unlocking, transferring ownership of the mutex between threads, and using condition_variable.

To avoid races, always use RAII wrappers where possible. Note that attempting to reacquire the same mutex will lead to deadlock (unless it is a recursive mutex).

Example of correct usage:

#include <iostream> #include <mutex> #include <thread> std::mutex mtx; void printThreadSafe(int id) { std::lock_guard<std::mutex> lock(mtx); std::cout << "Thread " << id << std::endl; } int main() { std::thread t1(printThreadSafe, 1); std::thread t2(printThreadSafe, 2); t1.join(); t2.join(); }

Trick question

Can std::lock_guardstd::mutex be used to implement conditional synchronization with std::condition_variable?

Answer: No! To work with std::condition_variable, std::unique_lockstd::mutex is necessary, because lock_guard does not provide methods for controlling the lock (for example, temporarily releasing — unlock — and then re-acquiring), which are required when waiting for a notification.

Example:

std::mutex mtx; std::condition_variable cv; bool ready = false; void waitForEvent() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{return ready;}); // Further processing }

Examples of real errors due to lack of knowledge of the nuances of the topic


Story

The project used manual handling with mtx.lock()/mtx.unlock(). When an exception was thrown between lock/unlock, the mutex remained locked — multiple threads "hung". After switching to RAII (std::lock_guard), the problem disappeared.


Story

When notifying via std::condition_variable, std::lock_guard was used instead of std::unique_lock. As a result, the program did not compile on new compilers, while on the old one, a runtime assert occurred due to inconsistent API.


Story

The code called unlock() on std::lock_guard for "manual" control over access to the mutex. This led to undefined behavior on different compilers, sometimes crashing the application. It was understood that lock_guard does not support manual release, migration to unique_lock was needed.