I am a bit confused about the role of std::unique_lock
when working with std::condition_variable
. As far as I understood the documentation, std::unique_lock
is basically a bloated lock guard, with the possibility to swap the state between two locks.
I've so far used pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
for this purpose (I guess that's what the STL uses on posix). It takes a mutex, not a lock.
What's the difference here? Is the fact that std::condition_variable
deals with std::unique_lock
an optimization? If so, how exactly is it faster?
It's essentially an API design decision to make the API as safe as possible by default (with the additional overhead being seen as negligible). By requiring to pass a
unique_lock
instead of a rawmutex
users of the API are directed towards writing correct code (in the presence of exceptions).In recent years the focus of the C++ language has shifted towards making it safe by default (but still allowing users to shoot themselves into their feet if they want to and try hard enough).
I upvoted cmeerw's answer because I believe he gave a technical reason. Let's walk through it. Let's pretend that the committee had decided to have
condition_variable
wait on amutex
. Here is code using that design:This is exactly how one shouldn't use a
condition_variable
. In the regions marked with:there is an exception safety problem, and it is a serious one. If an exception is thrown in these areas (or by
cv.wait
itself), the locked state of the mutex is leaked unless a try/catch is also put in somewhere to catch the exception and unlock it. But that's just more code you're asking the programmer to write.Let's say that the programmer knows how to write exception safe code, and knows to use
unique_lock
to achieve it. Now the code looks like this:This is much better, but it is still not a great situation. The
condition_variable
interface is making the programmer go out of his way to get things to work. There is a possible null pointer dereference iflk
accidentally does not reference a mutex. And there is no way forcondition_variable::wait
to check that this thread does own the lock onmut
.Oh, just remembered, there is also the danger that the programmer may choose the wrong
unique_lock
member function to expose the mutex.*lk.release()
would be disastrous here.Now let's look at how the code is written with the actual
condition_variable
API that takes aunique_lock<mutex>
:wait
function can checklk.owns_lock()
and throw an exception if it isfalse
.These are technical reasons that drove the API design of
condition_variable
.Additionally,
condition_variable::wait
doesn't take alock_guard<mutex>
becauselock_guard<mutex>
is how you say: I own the lock on this mutex untillock_guard<mutex>
destructs. But when you callcondition_variable::wait
, you implicitly release the lock on the mutex. So that action is inconsistent with thelock_guard
use case / statement.We needed
unique_lock
anyway so that one could return locks from functions, put them into containers, and lock/unlock mutexes in non-scoped patterns in an exception safe way, sounique_lock
was the natural choice forcondition_variable::wait
.Update
bamboon suggested in the comments below that I contrast
condition_variable_any
, so here goes:Question: Why isn't
condition_variable::wait
templated so that I can pass anyLockable
type to it?Answer:
That is really cool functionality to have. For example this paper demonstrates code that waits on a
shared_lock
(rwlock) in shared mode on a condition variable (something unheard of in the posix world, but very useful nonetheless). However the functionality is more expensive.So the committee introduced a new type with this functionality:
With this
condition_variable
adaptor one can wait on any lockable type. If it has memberslock()
andunlock()
, you are good to go. A proper implementation ofcondition_variable_any
requires acondition_variable
data member and ashared_ptr<mutex>
data member.Because this new functionality is more expensive than your basic
condition_variable::wait
, and becausecondition_variable
is such a low level tool, this very useful but more expensive functionality was put into a separate class so that you only pay for it if you use it.