On the reference documentation for std::condition_variable there's this example:
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
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 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);
data = "Example data";
// send data to the worker thread
{
std::lock_guard lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock lk(m);
cv.wait(lk, []{ return processed; });
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
My question is regarding very specifically this section:
{
std::lock_guard lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
Why does ready need to have a lock guard? If the other thread is waiting shouldn't it be guaranteed that ready = true happens before that other thread is woken up by notify_one?
I'm asking this to get insight into a problem I'm seeing a private code base that I cannot show here.
I'm even more confused if ready was an std::atomic then is the lock still needed? In the private code I observed that it still is otherwise the bool isn't changed before the notification and wakeup happens
Without the lock in question,
ready = true;is not synchronized withworker_threadreadingready. That's a data race and has undefined behavior.With the removed sychronization primitive, changes to
readyin one thread doesn't require the compiler to produce any code that makes sense to you as a programmer.The waiting thread may never see the change in
readyand the compiler may let the waiting thread observe a "cached" value offalse- since, again, you didn't tell the compiler that there's some synchronization needed. If the two threads do not synchronize, why then should the compiler assume that the value of the variable will ever change in the thread that reads it? If you don't tell it that there's a writer it may simply replace the reading of variable with a hard value, be ittrueorfalse.The compiler could also lay out the final assembly as if
readywas set all along. It's allowed to go any way it wants when you don't synchronize events.You also don't know when the waiting thread wakes up. It may not only be woken up by
notify_onesince there's also spurious wakeups, which is why the condition must be checked after waking up. If it's nottrue, go back to waiting.