The mutex guards the internal state of the condition_variable. Calling wait on the condition_variable causes the mutex to be unlocked. So while waiting, threads do not own the mutex.
When the wait completes, the mutex is again (atomically) acquired before the call to wait returns.
The threads are not contending on the mutex, they are contending on the condition itself.
You are free to unlock the lock as soon as you return from wait if you wish. If you want to allow multiple threads to synchronise on a condition, for example, this is how you would do it. You can also use this feature to implement a semaphore.
example:
This code processes things in batches of 10. Note that notify_all() goes after the unlock():
#include <condition_variable> #include <mutex> #include <iostream> #include <string> #include <thread> #include <chrono> #include <vector> void emit(std::string const& s) { static std::mutex m; auto lock = std::unique_lock<std::mutex>(m); std::cout << s << std::endl; } std::mutex m; std::condition_variable cv; int running_count = 0; void do_something(int i) { using namespace std::literals; auto lock = std::unique_lock<std::mutex>(m); // mutex is now locked cv.wait(lock, // until the cv is notified, the mutex is unlocked [] { // mutex has been locked here return running_count < 10; // if this returns false, mutex will be unlocked again, but code waits inside wait() for a notify() }); // mutex is locked here ++running_count; lock.unlock(); // we are doing work after unlocking the mutex so others can also // work when notified emit("running " + std::to_string(i)); std::this_thread::sleep_for(500ms); // manipulating the condition, we must lock lock.lock(); --running_count; lock.unlock(); // notify once we have unlocked - this is important to avoid a pessimisation. cv.notify_all(); } int main() { std::vector<std::thread> ts; for (int i = 0 ; i < 200 ; ++i) { ts.emplace_back([i] { do_something(i); }); } for (auto& t : ts) { if (t.joinable()) t.join(); } }