18

I am reading about std::condition_variable on http://en.cppreference.com/w/cpp/thread/condition_variable and I don't understand this:

Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.

Why a shared atomic variable is not properly published if it is not modified under mutex? How to understand this statement?

On another page http://en.cppreference.com/w/cpp/atomic/atomic there is a statement that seems to contradict to the the first statement:

If one thread writes to an atomic object while another thread reads from it, the behavior is well-defined (see memory model for details on data races)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084

1 Answers1

20

Consider this example:

std::atomic_bool proceed(false);
std::mutex m;
std::condition_variable cv;

std::thread t([&m,&cv,&proceed]()
{
    {
        std::unique_lock<std::mutex> l(m);
        while(!proceed) {
            hardWork();
            cv.wait(l);
        }
    }
});

proceed = true;
cv.notify_one();
t.join();

Here the atomic shared data proceed is modified without the use of a mutex, after which notification is sent to the condition variable. But it is possible that at the instant that the notification is sent, the thread t is not waiting on cv: instead it is inside hardWork() having checked proceed just before that and found it to be false. The notification is missed. When t completes hardWork, it will resume the wait (presumably forever).

Had the main thread locked the mutex before modifying the shared data proceed, the situation would have been avoided.

I think this is the situation in mind when saying "Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread."

Smeeheey
  • 9,906
  • 23
  • 39
  • 4
    This can happen even without `hardWork`. You can check `proceed`, find it to be false, then `proceed` can be set to `true`, then `notify_one` can be called, and then you can call `wait`. Again, you'd be waiting for something that already happened. The entire logic of condition variables is that they work in conjunction with the mutex that protects the shared state to provide an atomic "unlock and wait" operation. – David Schwartz Feb 21 '19 at 00:07
  • @DavidSchwartz What if add `lock_guard{m}` between `proceed = true;` and `cv.notify_one();`? If do that, `notify_one` will not be called before `wait`. – macomphy Jul 06 '22 at 07:35
  • @macomphy But then it makes no difference whether `proceed` is atomic or not because it is never accessed without holding the mutex. – David Schwartz Jul 06 '22 at 18:27
  • @DavidSchwartz Sorry, maybe I didn't express it clearly enough. I mean something like this: :https://godbolt.org/z/xcM1oqKjz. – macomphy Jul 07 '22 at 01:34