I need to be clarified how lock and condition_variable work.
In the -slightly modified- code from here cplusplusreference
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
std::this_thread::sleep_for(std::chrono::seconds(1));
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
The thing that I was confused with was how main thread could lock the mutex if worker_thread had already locked it.
From this answer I saw that it is because cv.wait
unlocks the mutex.
But now I am confused with this: So why do we need to lock it at all, if cv.wait
will unlock it?
For example, could I do this?
std::unique_lock<std::mutex> lk(m, std::defer_lock);
So, I create the lock object because cv needs it, but I do not lock it when I create.
Is there any difference now?
I did not see why I get "runtime error" here in that case.
Quoted from std::condition_variable::wait() :
Calling this function if lock.mutex() is not locked by the current thread is undefined behavior.
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.
I think your misunderstanding stems from a deeper misunderstanding of what locks are and how they interact with condition variables.
The basic reason a lock exists is to provide mutual exclusion. Mutual exclusion guarantees that certain parts of the code are only executed by a single thread. This is why you can't just wait with the lock until later - you need it locked to have your guarantees.
This causes problems when you want some other parts of the code to execute but still need to have mutual exclusion while the current piece of code executes. This is where condition variables come in handy: they provide a structured way to release the lock and be guaranteed that when you wake up again you will have it back. This is why the lock is unlocked while in the wait function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With