百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

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

bigegpt 2024-09-17 12:26 16 浏览

上一章介绍了新线程库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并发库提供了更加方便的使用多线程编程的库,大家可以多多了解使用,后续会介绍更多并发编程的特性。

相关推荐

Go语言泛型-泛型约束与实践(go1.7泛型)

来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...

golang总结(golang实战教程)

基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...

Go 官宣:新版 Protobuf API(go pro版本)

原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...

Golang开发的一些注意事项(一)(golang入门项目)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

golang 托盘菜单应用及打开系统默认浏览器

之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...

golang标准库每日一库之 io/ioutil

一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...

文件类型更改器——GoLang 中的 CLI 工具

我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...

Go (Golang) 中的 Channels 简介(golang channel长度和容量)

这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...

Golang引入泛型:Go将Interface「」替换为“Any”

现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...

一文带你看懂Golang最新特性(golang2.0特性)

作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...

Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移

以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...

Golang使用grpc详解(golang gcc)

gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...

Etcd服务注册与发现封装实现--golang

服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...

Golang:将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...

如何从 PHP 过渡到 Golang?(php转golang)

我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...