Home > Net >  Is owning the lock required to request a stop while waiting on a condition_variable_any with stop_to
Is owning the lock required to request a stop while waiting on a condition_variable_any with stop_to

Time:11-29

While waiting on a condition variable, the thread changing the state of the predicate must own the lock, so the update isn't missed during the wakeup. According to the documentation, this is necessary, even while using atomic variables. However I'm not certain if request_stop() already handles it correctly.

So the question is, which of the two options is the correct and standard conforming one?

~jthread() naturally doesn't take a lock to request_stop(), but then I don't understand where the difference between a stop_token and an atomic shared variable is. And thereby, how one of them requires the lock, while the other doesn't.

#include <thread>
#include <condition_variable>
#include <iostream>
#include <chrono>


std::mutex m;
std::condition_variable_any cv;

void waitingThread(std::stop_token st){
    std::unique_lock<std::mutex> lk(m);
    std::cout<<"Waiting"<<std::endl;
    cv.wait(lk, st, [](){return false;});
    std::cout<<"Awake"<<std::endl;
}

void withoutLock(){
    std::jthread jt{waitingThread};
    std::this_thread::sleep_for(std::chrono::seconds(1));
    jt.request_stop();
}

void withLock(){
    std::jthread jt{waitingThread};
    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::lock_guard<std::mutex> lk(m);
        jt.request_stop();
    }
}

int main(){
    withoutLock();
    withLock();
}

std::condition_variable specifies:

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

std::condition_variable_any::wait, std::stop_token and std::stop_source do not specify, if the interrupt of the wait is guaranteed register the change in the stop state.

CodePudding user response:

All stop_token overloads of condition_variable_any::wait and the timed variants, register a stop_callback, which captures the Lockable object. Then on request_stop() the callback is executed, taking the lock and notifying the waiting threads, therefore eliminating missing updates.

CodePudding user response:

While waiting on a condition variable, the thread changing the state of the predicate must own the lock, so the update isn't missed during the wakeup.

... do not specify, if the interrupt of the wait is guaranteed register the change in the stop state.

The issue isn't exactly missing the update, but going back to sleep after the notify has happened and before checking the predicate again.

The sequence in this answer is the problematic one.

According to the documentation, this is necessary, even while using atomic variables. However I'm not certain if request_stop() already handles it correctly.

The shared state itself is synchronized just fine: thread.stoptoken.intro/5 says

Calls to the functions request_­stop, stop_­requested, and stop_­possible do not introduce data races. A call to request_­stop that returns true synchronizes with a call to stop_­requested on an associated stop_­token or stop_­source object that returns true.

The wait-predicate loop described in thread.condvarany.intwait would have the same problem:

while (!stoken.stop_requested()) {
  if (pred())
    return true;
  wait(lock);
}
return pred();

if request_stop is called between the first line and the wait(lock), then stop_requested() will become true and the condition variable be notified while nobody is waiting. Then we'll start waiting, for a notification that never comes.

If request_stop can only be called while the mutex is released inside wait, we'll always be guaranteed to check stop_requested() before sleeping again.

In fact though, the GNU ISO C library does this for us: std::condition_variable_any has an additional internal mutex used to synchronize the request_stop callback (which just notifies the condvar in the requesting thread) correctly with the wait-predicate loop. So, if that behaviour is required by the standard, you don't need your own mutex here.

  • Related