Why do I need to acquire a lock to modify a shared "atomic" variable before notifying condition_variable [duplicate]
Asked Answered
A

2

12

Accoding to cppreference.com:

The thread that intends to modify the variable has to

  1. acquire a std::mutex (typically via std::lock_guard)
  2. perform the modification while the lock is held
  3. execute notify_one or notify_all on the std::condition_variable (the lock does not need to be held for notification)

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'm not quite understand, why modifying a atomic variable need to require an lock. Please see following code snippet:

static std::atomic_bool s_run {true};
static std::atomic_bool s_hasEvent {false};
static std::mutex s_mtx;
static std::condition_variabel s_cv;


// Thread A - the consumer thread
function threadA()
{
    while (s_run)
    {
        {
            std::unique_lock<std::mutex> lock(s_mtx);
            s_cv.wait(lock, [this]{
                return m_hasEvents.load(std::memory_order_relaxed);
            });
        }

        // process event
        event = lockfree_queue.pop();
        ..... code to process the event ....
    }
}


// Thread B - publisher thread
function PushEvent(event)
{
    lockfree_queque.push(event)
    s_hasEvent.store(true, std::memory_order_release);
    s_cv.notify_one();
}

In the PushEvent function, I do not acquire s_mtx because s_hasEvent is an atomic variable and the queue is lockfree. What is the problem w/o acquire the s_mtx lock?

Associationism answered 26/1, 2017 at 5:7 Comment(4)
Condition variables are shared state, therefore must be protected by a mutex.Fairlie
The question is about protecting the s_hasEvents by a mutex (which is also shared state) not the condvar. The publisher doesn't modify the condvar, it only calls notify_one() which doesn't need to be done while the mutex is locked.Marquee
@JonathanWakely answer should be acceptedClein
Possible duplicate of Why is there no wait function for condition_variable which does not relock the mutexSzombathely
M
12

As noted in Yakk's answer to the question you linked to it is to protect against this sequence of events causing a missed wake-up:

    1. Thread A locks the mutex.
    1. Thread A calls the lambda's closure which does m_hasEvents.load(std::memory_order_relaxed); and returns the value false.
    1. Thread A is interrupted by the scheduler and Thread B starts to run.
    1. Thread B pushes an event into the queue and stores to s_hasEvent
    1. Thread B runs s_cv.notify_one().
    1. Thread B is interrupted by the scheduler and Thread A runs again.
    1. Thread A evaluates the false result returned by the closure, deciding there are no pending events.
    1. Thread A blocks on the condition variable, waiting for an event.

This means the notify_one() call has been missed, and the condition variable will block even though there is an event ready in the queue.

If the update to the shared variable is done while the mutex is locked then it's not possible for the step 4 to happen between steps 2 and 7, so the condition variable's check for events gets a consistent result. With a mutex used by the publisher and the consumer either the store to s_hasEvent happens before step 1 (and so the closure loads the value true and never blocks on the condition variable) or it happens after step 8 (and so the notify_one() call will wake it up).

Marquee answered 26/1, 2017 at 14:8 Comment(3)
Yes. The mutex is required to prevent races with the condition variable. It has nothing to do with the condition itself, nor its atomicity.Ashcan
@JonathanWakely If we are however not using any predicate (i.e. no shared variable), we can be sure that a notification will not happen while "wait" is being executed, is that correct? Either notify will be called before it starts waiting, or after it's already waiting. I'm not sure whether this is the type "atomicity" that is meant when wait is described as atomic - 'atomic' could just be meant from the perspective of other threads that are assumed to use the same lock.Toad
@Toad I'm not sure exactly what scenario you're describing, but if a notification happens before wait then you miss the notification and block indefinitely. You still need the producer thread to lock the mutex when making work ready for the consumer thread, whether using a predicate or not. If you don't have some shared variable that a predicate can test, why are you even using a condition variable? What is the "condition" being waited for, if there's no shared data?Marquee
A
0

Found a very good explanation about this issue in another thread. Take a look at

Questions where asked below about race conditions.

If the data being communicated is atomic, can't we do without the mutex on the "send" side?

at the end.

Associationism answered 26/1, 2017 at 6:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.