Without the lock in question, ready = true;
is not synchronized with worker_thread
reading ready
. That's a data race and has undefined behavior.
{
// std::lock_guard lk(m); // removed
ready = true; // race condition
}
With the removed sychronization primitive, changes to ready
in 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 ready
and the compiler may let the waiting thread observe a "cached" value of false
- 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 it true
or false
.
The compiler could also lay out the final assembly as if ready
was 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_one
since there's also spurious wakeups, which is why the condition must be checked after waking up. If it's not true
, go back to waiting.