使用 std::stop_source 和 std::stop_token 而不是 std::atomic<bool> 进行延迟取消的好处?

And*_*ndy 6 c++ multithreading c++20

当我并行运行多个 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;
}
Run Code Online (Sandbox Code Playgroud)

然而现在我正在阅读关于std::stop_source和 的内容std::stop_token。我发现通过将 astd::stop_source按引用和std::stop_token按值传递给线程函数可以实现与上面相同的效果吗?那怎么会更优越呢?

据我所知,如果我想从线程外部停止线程,使用 是非常方便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;
}
Run Code Online (Sandbox Code Playgroud)

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

std::jthread t1(&threadFunction, 1, source);
std::jthread t2(&threadFunction, 2, source);
Run Code Online (Sandbox Code Playgroud)

但这似乎不起作用。

Nic*_*las 6

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

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

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

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

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