繁体   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?

当我并行运行多个 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;
}

但是现在我正在阅读有关std::stop_sourcestd::stop_token的信息。 我发现我可以通过将std::stop_source引用和std::stop_token值传递给线程 function 来实现与上述相同的效果? 那怎么会优越呢?

我知道在使用std::jthread时,如果我想从线程外部停止线程,则std::stop_token非常方便。 然后我可以从主程序调用std::jthread::request_stop()

但是,在我想从线程中停止线程的情况下,它还是更好吗?

我设法使用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;
}

使用std::jthread会产生更紧凑的代码:

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

但这似乎不起作用。

它不起作用,因为std::jthread有一个特殊功能,如果线程函数的第一个参数是std::stop_token ,它会通过内部stop_source object 填充该令牌。

您应该做的只是传递一个stop_source (按值,而不是按引用),并在您的线程 function 中从中提取令牌。

至于为什么这比引用原子更好,有无数的原因。 首先是stop_source比对 object 的裸引用安全得多,后者的生命周期不受线程 function 的本地控制。 第二个是你不必做std::ref体操来传递参数。 这可能是错误的来源,因为您可能会在某些地方不小心忘记这样做。

标准的stop_token机制不仅具有请求和响应停止的功能。 由于对停止的响应发生在发出停止后的任意时间,因此可能需要在实际请求停止时而不是在响应停止时执行某些代码。 stop_callback机制允许您使用stop_token注册回调。 此回调将在stop_source::request_stop调用的线程中调用(除非您在请求停止注册回调,在这种情况下,它会在您注册时立即调用)。 这在有限的情况下很有用,而且它不是你自己编写的简单代码。 尤其是当你只有一个atomic<bool>时。

然后是简单的可读性。 传递stop_source可以准确地告诉您发生了什么,甚至无需查看参数的名称。 传递atomic<bool>仅从类型名告诉您的信息很少; 您必须查看 function 中的参数名称或其用法才能知道它是用于暂停线程。

除了更具表现力和更好地传达意图之外, stop_token和朋友还为jthread实现了一些非常重要的东西。 要理解它,你必须考虑它的析构函数,它看起来像这样:

~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();
    }
}

通过封装stop_sourcejthread促进了所谓的协作取消 正如您还注意到的,您不必将stop_token传递给jthread ,只需提供一个接受令牌作为其第一个参数的回调。 接下来发生的事情是 class 可以检测到您的回调接受停止令牌并在调用它时将令牌传递给其内部停止源。

这对合作取消意味着什么? 当然更安全的终止! 由于jthread将始终尝试在销毁时join ,因此它现在可以防止两个或多个线程相互等待完成的无限循环和死锁。 通过使用stop_token ,您的代码可以确保它可以安全地加入go

但是,在我想从线程中停止线程的情况下,它还是更好吗?

现在关于您请求的功能,这就是 C# 所说的“链接取消”。 是的,有一些请求和讨论要在jthread构造函数中添加一个参数,以便它可以引用外部停止源,但这还不可用(并且有很多含义)。 纯粹使用停止令牌做类似的事情需要一个stop_callback将所有取消联系在一起,但它仍然可能不是最佳的(如链接中所示)。 底线是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. 

好消息是,如果您没有陷入链接中描述的次优模式(即您不需要异步终止),那么此功能很容易抽象为实用程序,例如:

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

您将用作

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.

现在,如果您想从线程内控制链接的线程,我将使用初始模式( std::atomic<bool> ),因为同时具有停止标记和停止源的回调有点令人困惑。

暂无
暂无

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

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