简体   繁体   中英

std::condition_variable memory writes visibility

Does std::condition_variable::notify_one() or std::condition_variable::notify_all() guarantee that non-atomic memory writes in the current thread prior to the call will be visible in notified threads?

Other threads do:

{
    std::unique_lock lock(mutex);
    cv.wait(lock, []() { return values[threadIndex] != 0; });
    // May a thread here see a zero value and therefore start to wait again?
}

Main thread does:

fillData(values); // All values are zero and all threads wait() before calling this.
cv.notify_all(); // Do need some memory fence or lock before this
                 // to ensure that new non-zero values will be visible
                 // in other threads immediately after waking up?

Doesn't notify_all() store some atomic value therefore enforcing memory ordering? I did not clarified it.

UPD : according to Superlokkus' answer and an answer here : we have to acquire a lock to ensure memory writes visibility in other threads (memory propagation), otherwise threads in my case may read zero values.

Also I missed this quote here about condition_variable , which specifically answers my question. Even an atomic variable has to be modified under a lock in a case when the modification must become visible immediately.

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.

I guess you are mixing up memory ordering of so called atomic values and the mechanisms of classic lock based synchronization.

When you have a datum which is shared between threads, lets say an int for example, one thread can not simply read it while the other thread might be write to it meanwhile. Otherwise we would have a data race.

To get around this for long time we used classic lock based synchronization: The threads share at least a mutex and the int . To read or to write any thread has to hold the lock first, meaning they wait on the mutex. Mutexes are build so that they are fine that this can happen concurrently. If a thread wins gettting the mutex it can change or read the int and then should unlock it, so others can read/write too. Using a conditional variable like you used is just to make the pattern "readers wait for a change of a value by a writer" more efficient, they get woken up by the cv instead of periodically waiting on the lock, reading, and unlocking, which would be called busy waiting.

So because you hold the lock in any after waiting on the mutex or in you case, correctly (mutex is still needed) waiting on the conditional variable, you can change the int . And readers will read the new value after the writer was able to wrote it, never the old. UPDATE : However one thing if have to add, which might also be the cause of confusion: Conditional variables are subject for so called spurious wakeups. Meaning even though you write did not have notified any thread, a read thread might still wake up, with the mutex locked. So you have to check if you writer actually waked you up, which is usually done by the writer by changing another datum just to notify this, or if its suitable by using the same datum you already wanted to share. The lambda parameter overload of std::condition_variable::wait was just made to make the checking and going back to sleep code looking a bit prettier. Based on your question now I don't know if you want to use you values for this job.

However at snippet for the "main" thread is incorrect or incomplete: You are not synchronizing on the mutex in order to change values . You have to hold the lock for that, but notifying can be done without the lock.

std::unique_lock lock(mutex);
fillData(values); 
lock.unlock();
cv.notify_all(); 

But these mutex based patters have some drawbacks and are slow, only one thread at a time can do something. This is were so called atomics, like std::atomic<int> came into play. They can be written and read at the same time without an mutex by multiple threads concurrently. Memory ordering is only a thing to consider there and an optimization for cases where you uses several of them in a meaningful way or you don't need the "after the write, I never see the old value" guarantee. However with it's default memory ordering memory_order_seq_cst you would also be fine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM