简体   繁体   English

在使用 stop_token 等待 condition_variable_any 时是否需要拥有请求停止的锁?

[英]Is owning the lock required to request a stop while waiting on a condition_variable_any with stop_token?

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.在等待条件变量时,更改谓词 state 的线程必须拥有锁,因此在唤醒期间不会错过更新。 According to the documentation, this is necessary, even while using atomic variables.根据文档,这是必要的,即使在使用原子变量时也是如此。 However I'm not certain if request_stop() already handles it correctly.但是我不确定request_stop()是否已经正确处理了它。

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. ~jthread()自然不会锁定request_stop() ,但后来我不明白stop_token和原子共享变量之间的区别在哪里。 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: std::condition_variable指定:

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. std::condition_variable_any::waitstd::stop_tokenstd::stop_source不指定,如果等待中断被保证注册停止 state 中的变化。

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.在等待条件变量时,更改谓词 state 的线程必须拥有锁,因此在唤醒期间不会错过更新。

... do not specify, if the interrupt of the wait is guaranteed register the change in the stop state. ...不指定,如果等待的中断是保证注册停止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.问题不完全是缺少更新,而是在notify发生后和再次检查谓词之前回到睡眠状态。

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.但是我不确定request_stop()是否已经正确处理了它。

The shared state itself is synchronized just fine: thread.stoptoken.intro/5 says共享的 state 本身同步得很好: thread.stoptoken.intro/5

Calls to the functions request_stop , stop_requested , and stop_possible do not introduce data races.调用函数request_stopstop_requestedstop_possible不会引入数据竞争。 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.对返回 true 的request_stop的调用与对返回true的关联stop_tokenstop_source stop_requested上的 stop_requested 的调用同步。

The wait-predicate loop described in thread.condvarany.intwait would have the same problem: thread.condvarany.intwait中描述的等待谓词循环也会有同样的问题:

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.如果在第一行和wait(lock)之间调用request_stop ,则stop_requested()将变为true并且在没有人等待时通知条件变量。 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.如果request_stop只能在wait中释放互斥量时调用,我们将始终保证在再次休眠之前检查stop_requested()

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.事实上,GNU ISO C++ 库为我们做了这件事: std::condition_variable_any有一个额外的内部互斥锁,用于将request_stop回调(它只是通知请求线程中的 condvar)与等待谓词循环正确同步。 So, if that behaviour is required by the standard, you don't need your own mutex here.因此,如果该行为是标准所要求的,那么您在这里就不需要自己的互斥锁。

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. condition_variable_any::wait和定时变体的所有stop_token重载,注册一个stop_callback ,它捕获 Lockable object。然后在request_stop()上执行回调,获取锁并通知等待线程,从而消除丢失的更新。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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