专业的编程技术博客社区

网站首页 > 博客文章 正文

C++20并发库新成员jthread(续)

baijin 2025-08-03 04:03:39 博客文章 2 ℃ 0 评论

上一章介绍了新线程库jthread的简单使用以及和thread库的区别,此章继续介绍和jthread相关并发新特性,stop_token、stop_source、stop_callback。这些方法提供给线程之间同步停止状态方式。

  1. jthread类提供了三个接口来处理停止token,如下。
get_stop_source  // 获取和本线程停止状态关联的stop_resource
get_stop_token    // 获取和本线程停止状态关联的stop_token
request_stop         // 请求修改停止状态为停止

jthread中使用上面上个接口的示例如下:

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

int main(int argc, char const *argv[])
{
    auto f = [] (std::stop_token st) {
        for (size_t i = 0; i < 10 && !st.stop_requested(); i++) {  // 通过st获取停止状态
            std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    };
    std::jthread t0(f);

    std::this_thread::sleep_for(std::chrono::milliseconds(3000));
    t0.request_stop();   // 等价于 t0.get_stop_source().request_stop();

    return 0;
}

对与例子中的f函数,第一个参数是std::stop_token,这个参数可以不用传,jthread调用的时候会把与自身停止状态关联的stop_token传进去,当我们调用线程的request_stop()函数后就可以通过次stop_token获取到是否已被请求停止。我们也可以自己传stop_token进去,可以自定义stop_resource并且获取stop_token。

  1. stop_resource

stop_source 类提供了发出停止请求的方法,例如用于 std::jthread 取消。针对一个 stop_source 对象发出的停止请求,对于所有具有相同关联停止状态的 stop_sources 和 std::stop_tokens 都是可见的;任何注册到关联 std::stop_token(s)的 std::stop_callback(s)都会被调用,并且任何等待关联 std::stop_token(s)的
std::condition_variable_any 对象都会被唤醒。一旦发出停止请求,就无法撤回。额外的停止请求没有效果。

#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>
 
using namespace std::chrono_literals;  // 时间字面量,在literals文章中有介绍
 
void worker_fun(int id, std::stop_source stop_source){
    std::stop_token stoken = stop_source.get_token();
    for (int i = 10; i; --i) {
        std::this_thread::sleep_for(300ms);
        if (stoken.stop_requested()) {
            std::printf("  worker%d is requested to stop\n", id);
            return;
        }
        std::printf("  worker%d goes back to sleep\n", id);
    }
}
 
int main() {
    std::jthread threads[4];
    std::cout << std::boolalpha;
    auto print = [](const std::stop_source& source) {
        std::printf("stop_source stop_possible = %s, stop_requested = %s\n",
                    source.stop_possible() ? "true" : "false",
                    source.stop_requested() ? "true" : "false");
    };
 
    // Common source
    std::stop_source stop_source;
 
    print(stop_source);
 
    // Create worker threads
    for (int i = 0; i < 4; ++i)
        threads[i] = std::jthread(worker_fun, i + 1, stop_source);
 
    std::this_thread::sleep_for(500ms);
 
    std::puts("Request stop");
    stop_source.request_stop();
 
    print(stop_source);
 
    // Note: destructor of jthreads will call join so no need for explicit calls
}
  1. stop_token

stop_token 类提供了检查针对其关联的 std::stop_source 对象是否已经发出或可以发出停止请求的方法,它本质上是关联停止状态的线程安全“视图”。

stop_token 也可以传递给 std::stop_callback 的构造函数,以便在 stop_token 关联的 std::stop_source 被请求停止时调用回调。并且 stop_token 可以传递给
std::condition_variable_any 的可中断等待函数,以在请求停止时中断条件变量的等待。

如下示例:

#include <iostream>
#include <thread>
 
using namespace std::literals::chrono_literals;
 
void f(std::stop_token stop_token, int value) {
    while (!stop_token.stop_requested()) {
        std::cout << value++ << ' ' << std::flush;
        std::this_thread::sleep_for(200ms);
    }
    std::cout << std::endl;
}
 
int main() {
    std::jthread thread(f, 5);   // 可以不用传stop_token参数,jthread自动填充
    std::this_thread::sleep_for(3s);
}
  1. stop_callback

stop_callback 类模板提供了一个 RAII 对象类型,用于注册关联的 std::stop_token 对象的回调函数,以便在请求停止关联的 std::stop_source 时调用回调函数。

通过 stop_callback 的构造函数注册的回调函数,将在成功调用 stop_callback 关联的 std::stop_token 的 std::stop_source 的 request_stop() 的同一线程中被调用;或者如果在构造函数注册之前已经请求停止,则回调将在构造 stop_callback 的线程中被调用。

可以为同一个 std::stop_token 创建多个 stop_callback,它们可以来自同一个或不同的线程,并且可以同时创建。不能保证它们执行的顺序,但它们将被同步调用;除非 stop_callback(s)是在已经为 std::stop_token 请求停止之后构造的,如前所述。

如果回调的调用通过异常退出,则调用 std::terminate。std::stop_callback 不可复制构造、复制赋值、移动构造或移动赋值。模板参数 Callback 类型必须既是可调用的又是可销毁的。任何返回值都将被忽略。

示例代码如下:

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
 
using namespace std::chrono_literals;
 
// 打印帮助类,帮助缓存所有日志,在被析构时打印所有日志.
class Writer {
    std::ostringstream buffer;
public:
    ~Writer() { std::cout << buffer.str(); }
    Writer& operator<<(auto input) { buffer << input; return *this; }
};
 
int main() {
    std::jthread worker([] (std::stop_token stoken) {
        Writer() << "Worker thread's id: " << std::this_thread::get_id() << '\n';
        std::mutex mutex;
        std::unique_lock lock(mutex);
        std::condition_variable_any().wait(lock, stoken,
            [&stoken] { return stoken.stop_requested(); });  // 这里当调用和stoken关联的stop_resource被调用request_stop()时,会唤醒线程,然后判断退出条件
    });

    // 注册回调函数
    std::stop_callback callback(worker.get_stop_token(), [] {
        Writer() << "Stop callback executed by thread: "
            << std::this_thread::get_id() << '\n';
    });
 
    auto stopper_func = [&worker] {
        if (worker.request_stop()) // 其中一个线程会调用成功
            Writer() << "Stop request executed by thread: "
                << std::this_thread::get_id() << '\n';
        else
            Writer() << "Stop request not executed by thread: "
                << std::this_thread::get_id() << '\n';
    };
 
    // 多个线程请求停止worker线程.
    std::jthread stopper1(stopper_func);
    std::jthread stopper2(stopper_func);
    stopper1.join();
    stopper2.join();
 
    // worker线程已经被调用了request_stop(), 主线程注册回调函数会立即被执行。
    Writer() << "Main thread: " << std::this_thread::get_id() << '\n';
    std::stop_callback callback_after_stop(worker.get_stop_token(), [] {
        Writer() << "Stop callback executed by thread: "
            << std::this_thread::get_id() << '\n';
    });
}

C++20并发库提供了更加方便的使用多线程编程的库,大家可以多多了解使用,后续会介绍更多并发编程的特性。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表