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
, andstop_possible
do not introduce data races. A call torequest_stop
that returnstrue
synchronizes with a call tostop_requested
on an associatedstop_token
orstop_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.