简体   繁体   English

使用 std::stop_source 和 std::stop_token 代替 std::atomic 的好处<bool>延期取消?</bool>

[英]Benefits of using std::stop_source and std::stop_token instead of std::atomic<bool> for deferred cancellation?

When I run several std::threads in parallell and need to cancel other threads in a deferred manner if one thread fails I use a std::atomic<bool> flag:当我并行运行多个 std::threads 并且如果一个线程失败时需要以延迟方式取消其他线程时,我使用std::atomic<bool>标志:

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

void threadFunction(unsigned int id, std::atomic<bool>& terminated) {
    srand(id);
    while (!terminated) {
        int r = rand() % 100;
        if (r == 0) {
            std::cerr << "Thread " << id << ": an error occured.\n";
            terminated = true; // without this line we have to wait for other thread to finish
            return;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main()
{
    std::atomic<bool> terminated = false;
    std::thread t1(&threadFunction, 1, std::ref(terminated));
    std::thread t2(&threadFunction, 2, std::ref(terminated));

    t1.join();
    t2.join();
    std::cerr << "Both threads finished.\n";
    int k;
    std::cin >> k;
}

However now I am reading about std::stop_source and std::stop_token .但是现在我正在阅读有关std::stop_sourcestd::stop_token的信息。 I find that I can achieve the same as above by passing both a std::stop_source by reference and std::stop_token by value to the thread function?我发现我可以通过将std::stop_source引用和std::stop_token值传递给线程 function 来实现与上述相同的效果? How would that be superior?那怎么会优越呢?

I understand that when using std::jthread the std::stop_token is very convenient if I want to stop threads from outside the threads.我知道在使用std::jthread时,如果我想从线程外部停止线程,则std::stop_token非常方便。 I could then call std::jthread::request_stop() from the main program.然后我可以从主程序调用std::jthread::request_stop()

However in the case where I want to stop threads from a thread is it still better?但是,在我想从线程中停止线程的情况下,它还是更好吗?

I managed to achieve the same thing as in my code using std::stop_source :我设法使用std::stop_source实现了与我的代码相同的事情:

 void threadFunction(std::stop_token stoken, unsigned int id, std::stop_source source) {
    srand(id);
    while (!stoken.stop_requested()) {
        int r = rand() % 100;
        if (r == 0) {
            std::cerr << "Thread " << id << ": an error occured.\n";
            source.request_stop(); // without this line we have to wait for other thread to finish
            return;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
int main()
{
    std::stop_source source;
    std::stop_token stoken = source.get_token();
    std::thread t1(&threadFunction, stoken, 1, source);
    std::thread t2(&threadFunction, stoken, 2, source);
    t1.join();
    t2.join();
    std::cerr << "Both threads finished.\n";
    int k;
    std::cin >> k;
}

Using std::jthread would have resulted in more compact code:使用std::jthread会产生更紧凑的代码:

std::jthread t1(&threadFunction, 1, source);
std::jthread t2(&threadFunction, 2, source);

But that did not seem to work.但这似乎不起作用。

It didn't work because std::jthread has a special feature where, if the first parameter of a thread-function is a std::stop_token , it fills that token in by an internal stop_source object.它不起作用,因为std::jthread有一个特殊功能,如果线程函数的第一个参数是std::stop_token ,它会通过内部stop_source object 填充该令牌。

What you ought to do is only pass a stop_source (by value, not by reference), and extract the token from it within your thread function.您应该做的只是传递一个stop_source (按值,而不是按引用),并在您的线程 function 中从中提取令牌。

As for why this is better than a reference to an atomic, there are a myriad of reasons.至于为什么这比引用原子更好,有无数的原因。 The first being that stop_source is a lot safer than a bare reference to an object whose lifetime is not under the local control of the thread function.首先是stop_source比对 object 的裸引用安全得多,后者的生命周期不受线程 function 的本地控制。 The second being that you don't have to do std::ref gymnastics to pass parameters.第二个是你不必做std::ref体操来传递参数。 This can be a source of bugs since you might accidentally forget to do that in some place.这可能是错误的来源,因为您可能会在某些地方不小心忘记这样做。

The standard stop_token mechanism has features beyond just requesting and responding to a stop.标准的stop_token机制不仅具有请求和响应停止的功能。 Since the response to a stop happens at an arbitrary time after issuing it, it may be necessary to execute some code when the stop is actually requested rather than when it is responded to.由于对停止的响应发生在发出停止后的任意时间,因此可能需要在实际请求停止时而不是在响应停止时执行某些代码。 The stop_callback mechanism allows you to register a callback with a stop_token . stop_callback机制允许您使用stop_token注册回调。 This callback will be called in the thread of the stop_source::request_stop call (unless you register the callback after the stop was requested, in which case it's called right when you register it).此回调将在stop_source::request_stop调用的线程中调用(除非您在请求停止注册回调,在这种情况下,它会在您注册时立即调用)。 This can be useful in limited cases, and it's not simple code to write yourself.这在有限的情况下很有用,而且它不是你自己编写的简单代码。 Especially when all you have is an atomic<bool> .尤其是当你只有一个atomic<bool>时。

And then there's simple readability.然后是简单的可读性。 Passing a stop_source tells you exactly what is going on without having to even see the name of a parameter.传递stop_source可以准确地告诉您发生了什么,甚至无需查看参数的名称。 Passing an atomic<bool> tells you very little from just the typename;传递atomic<bool>仅从类型名告诉您的信息很少; you have to look at the parameter name or its usage in the function to know that it is for halting the thread.您必须查看 function 中的参数名称或其用法才能知道它是用于暂停线程。

Apart from being more expressive and communicating intentions better, stop_token and friends achieve something really important for jthread .除了更具表现力和更好地传达意图之外, stop_token和朋友还为jthread实现了一些非常重要的东西。 To understand it you have to consider its destructor which looks something like this:要理解它,你必须考虑它的析构函数,它看起来像这样:

~jthread() 
{
    if(joinable()) 
    {
        // Not only user code, but the destructor as well
        // will let your callback know it's time to go.
        request_stop(); 
        
        join();
    }
}

by encapsulating a stop_source , jthread facilitates what is called cooperative cancellation .通过封装stop_sourcejthread促进了所谓的协作取消 As you've also noted, you never have to pass the stop_token to a jthread , just provide a callback that accepts the token as its first parameter.正如您还注意到的,您不必将stop_token传递给jthread ,只需提供一个接受令牌作为其第一个参数的回调。 What happens next is that the class can detect that your callback accepts a stop token and pass a token to its internal stop source when calling it.接下来发生的事情是 class 可以检测到您的回调接受停止令牌并在调用它时将令牌传递给其内部停止源。

What does this mean for cooperative cancellation?这对合作取消意味着什么? Safer termination of course!当然更安全的终止! Since jthread will always attempt to join on destruction, it now has the means to prevent endless loops and deadlocks where two or more threads wait for each other to finish.由于jthread将始终尝试在销毁时join ,因此它现在可以防止两个或多个线程相互等待完成的无限循环和死锁。 By using stop_token your code can make sure that it can safely join when it's time to go .通过使用stop_token ,您的代码可以确保它可以安全地加入go

However in the case where I want to stop threads from a thread is it still better?但是,在我想从线程中停止线程的情况下,它还是更好吗?

Now regarding the feature you are requesting, that's what C# calls "linked cancellation".现在关于您请求的功能,这就是 C# 所说的“链接取消”。 Yes, there are requests and discussions to add a parameter in the jthread constructor so that it can refer to an external stop source, but that's not yet available (and has many implications).是的,有一些请求和讨论要在jthread构造函数中添加一个参数,以便它可以引用外部停止源,但这还不可用(并且有很多含义)。 Doing something similar purely with stop tokens would require a stop_callback to tie all cancellations together, but still it could be suboptimal (as shown in the link).纯粹使用停止令牌做类似的事情需要一个stop_callback将所有取消联系在一起,但它仍然可能不是最佳的(如链接中所示)。 The bottom line is that jthread needs stop_token , but in some cases you may not need jthread, especially if the following solution does not appeal to you:底线是jthread需要stop_token ,但在某些情况下可能不需要 jthread ,特别是如果以下解决方案不吸引您:

stop_source ssource;
std::stop_callback cb {ssource.get_token(), [&] { 
   t1.request_stop();
   t2.request_stop(); 
}};

ssource.request_stop(); // This stops boths threads. 

The good news is that if you don't fall into the suboptimal pattern described in the link (ie you don't need an asynchronous termination), then this functionality is easy to abstract into a utility, something like:好消息是,如果您没有陷入链接中描述的次优模式(即您不需要异步终止),那么此功能很容易抽象为实用程序,例如:

auto linked_cancellations = [](auto&... jthreads) {
  stop_source s;
  return std::make_pair(s, std::stop_callback{
    s.get_token(), [&]{ (jthreads.request_stop(), ...); }}); 
};

which you'd use as您将用作

auto [stop_source, cb] = linked_cancellations(t1, t2); 
// or as many thread objects as you want to link ^^^

stop_source.request_stop(); // Stops all the threads that you linked.

Now if you want to control the linked threads from within the thread, I'd use the initial pattern ( std::atomic<bool> ), since having a callback with both a stop token and a stop source is somewhat confusing.现在,如果您想从线程内控制链接的线程,我将使用初始模式( std::atomic<bool> ),因为同时具有停止标记和停止源的回调有点令人困惑。

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

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