C++11: why does std::condition_variable use std::unique_lock?
so there is no technical reason?
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 a mutex
. Here is code using that design:
void foo()
{
mut.lock();
// mut locked by this thread here
while (not_ready)
cv.wait(mut);
// mut locked by this thread here
mut.unlock();
}
This is exactly how one shouldn't use a condition_variable
. In the regions marked with:
// mut locked by this thread here
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:
void foo()
{
unique_lock<mutex> lk(mut);
// mut locked by this thread here
while (not_ready)
cv.wait(*lk.mutex());
// mut locked by this thread here
}
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 if lk
accidentally does not reference a mutex. And there is no way for condition_variable::wait
to check that this thread does own the lock on mut
.
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 a unique_lock<mutex>
:
void foo()
{
unique_lock<mutex> lk(mut);
// mut locked by this thread here
while (not_ready)
cv.wait(lk);
// mut locked by this thread here
}
- This code is as simple as it can get.
- It is exception safe.
- The
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 a lock_guard<mutex>
because lock_guard<mutex>
is how you say: I own the lock on this mutex until lock_guard<mutex>
destructs. But when you call condition_variable::wait
, you implicitly release the lock on the mutex. So that action is inconsistent with the lock_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, so unique_lock
was the natural choice for condition_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 any Lockable
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:
`condition_variable_any`
With this condition_variable
adaptor one can wait on any lockable type. If it has members lock()
and unlock()
, you are good to go. A proper implementation of condition_variable_any
requires a condition_variable
data member and a shared_ptr<mutex>
data member.
Because this new functionality is more expensive than your basic condition_variable::wait
, and because condition_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.
Why does std::condition_variable take a unique_lock instead of a lock_guard?
The condition variable needs to be able to lock and unlock the mutex, lock_guard
doesn't allow this. lock_guard
also doesn't allow access to the mutex itself which most condition variable implementations probably require.
Why does std::condition_variable wait() require a std::unique_lock arg?
You need a lock to prevent this common newbie mistake:
- Producer thread produces something,
- Producer thread calls
some_condition.notify_all()
, - Producer thread goes idle for a while,
meanwhile:
- Consumer thread calls
some_condition.wait(...)
- Consumer thread waits,...
- And waits,...
- And waits.
A condition variable is not a flag. It does not remember that it was notified. If the producer calls notify_one()
or notify_all()
before the consumer has entered the wait()
call, then the notification is "lost."
In order to prevent lost notifications, there must be some shared data that tells the consumer whether or not it needs to wait, and there must be a lock to protect the shared data.
The producer should:
- Lock the lock,
- update the shared data,
- notify the condition variable,
- release the lock
The consumer must then:
- Lock the lock,
- Check the shared data to see if it needs wait,
- Wait if needed,
- consume whatever,
- release the lock.
The consumer needs to pass the lock in to the wait(...)
call so that wait(...)
can temporarily unlock it, and then re-lock it before returning. If wait(...)
did not unlock the lock, then the producer would never be able to reach the notify()
call.
How do std::unique_lock and std::condition_variable work
I will try to add a bit more explanation as to WHY condition variables require a lock.
You have to have a lock because your code needs to check that the condition predicate is true. The predicate is some value or combination of values that has to be true in order to continue. It could be a pointer that is NULL or points to a completed data structure ready for use.
You have to lock it AND CHECK the predicate before waiting because by the time you start waiting for the condition another thread might have already set it.
The condition notify and the wait returning does NOT mean that the condition is true. It only means that the condition WAS true at some time. It might even have been true, then false, then true again. It might also mean that your thread had been in an unrelated signal handler that caused the condition wait to break out. Your code does not even know how many times the condition notify has been called.
So once the condition wait returns it LOCKS the mutex. Now your code can check the condition while safely in the lock. If true then the code can update what it needs to update and release the lock. If it wasn't true it just goes back to the condition wait to try again. For example, it could take that data structure pointer and copy it into a vector, then set the lock protected pointer back to NULL.
Think of a condition as a way to make a polling loop more efficient. Your code still has to do all of the things it would do running in a loop waiting, except that it can go to sleep instead of non-stop spinning.
Is possible to use std::condition_variable with std::lock_guard?
No, a std::unique_lock
is needed if it is used with std::condition_variable
. std::lock_guard
may have less overhead, but it cannot be used with std::condition_variable
.
But the std::unique_lock
doesn't need to be manually unlocked, it also unlocks when it goes out of scope, like std::lock_guard
. So the waiting code could be written as:
std::mutex a_mutex;
std::condition_variable a_condition_variable;
{
std::unique_lock<std::mutex> a_lock(a_mutex);
a_condition_variable.wait(a_lock, [this] {return something;});
//Do something
}
See http://en.cppreference.com/w/cpp/thread/unique_lock
std::condition_variable why does it need a std::mutex
The mutex protects the predicate, that is, the thing that you are waiting for. Since the thing you are waiting for is, necessarily, shared between threads, it must be protected somehow.
In your example above, i == 1
is the predicate. The mutex protects i
.
It may be helpful to take a step back and think about why we need condition variables. One thread detects some state that prevents it from making forward progress and needs to wait for some other thread to change that state. This detection of state has to take place under a mutex because the state must be shared (otherwise, how could another thread change that state?).
But the thread can't release the mutex and then wait. What if the state changed after the mutex was released but before the thread managed to wait? So you need an atomic "unlock and wait" operation. That's specifically what condition variables provide.
With no mutex, what would they unlock?
The choice of whether to signal the condition variable before or after releasing the lock is a complex one with advantages on both sides. Generally speaking, you will get better performance if you signal while holding the lock.
std::unique_lockstd::mutex or std::lock_guardstd::mutex?
The difference is that you can lock and unlock a std::unique_lock
. std::lock_guard
will be locked only once on construction and unlocked on destruction.
So for use case B you definitely need a std::unique_lock
for the condition variable. In case A it depends whether you need to relock the guard.
std::unique_lock
has other features that allow it to e.g.: be constructed without locking the mutex immediately but to build the RAII wrapper (see here).
std::lock_guard
also provides a convenient RAII wrapper, but cannot lock multiple mutexes safely. It can be used when you need a wrapper for a limited scope, e.g.: a member function:
class MyClass{
std::mutex my_mutex;
void member_foo() {
std::lock_guard<mutex_type> lock(this->my_mutex);
/*
block of code which needs mutual exclusion (e.g. open the same
file in multiple threads).
*/
//mutex is automatically released when lock goes out of scope
}
};
To clarify a question by chmike, by default std::lock_guard
and std::unique_lock
are the same.
So in the above case, you could replace std::lock_guard
with std::unique_lock
. However, std::unique_lock
might have a tad more overhead.
Note that these days (since, C++17) one should use std::scoped_lock
instead of std::lock_guard
.
Related Topics
Bstr to Std::String (Std::Wstring) and Vice Versa
How to Treat a Specific Warning as an Error
How to Print the Address of Char Array
How to Set Given Channel of a Cv::Mat to a Given Value Efficiently Without Changing Other Channels
Static Variable Used in a Template Function
The Proper Way of Forcing a 32-Bit Compile Using Cmake
In C++, Can a Class with a Const Data Member Not Have a Copy Assignment Operator
C++ Passing an Array Pointer as a Function Argument
What Is the Purpose of Forward Declaration
Simple String Parsing with C++
C/C++: Casting Away Volatile Considered Harmful
Is It Possible for the Executable to Ask for Administrator Rights? (Windows 7)
Getting a List of User Profiles on a Computer in C++ Win32
How Do C++ Progs Get Their Return Value, When a Return Is Not Specified in the Function