有了std::thread,为什么还需要引入std::jthread?

news2024/11/22 16:19:37

C++进阶专栏:http://t.csdnimg.cn/HGkeZ

目录

1.前言

2.std::is_invocable_v

3.std::jthread

3.1.构造函数

3.2.std::jthread无需join/detach使用实例

3.3.std::jthread处理外部请求中断实

3.4.处理中断请求示例代码

4.特性

5.总结


1.前言

C++11以来提供了C++原生的多线程std::thread,这极大的方便了多线程的书写。在此之前书写多线程时需要平台原生API,这对于跨平台尤其是跨多平台程序来讲,多线程部分代码书写及维护都是极大的工作量。std::thread具有非常高的优势,但是其也有自己的缺点,以下代码为例:

void using_thread_with_no_join()
{
  std::thread t{[](){
    std::cout<<"sub thread xecate, thread id"<<std::this_thread::get_id();
  }};
}

运行如上代码时,会出现崩溃,堆栈信息如下:

        由如上堆栈信息可知,崩溃原因为std::thread在析构时,如果对象仍为joinable状态,则会触发中断,为避免崩溃需要在std::thread析构器前需要将其置于非joinable状态,即需要主动调用join或detach接口。如果忘记了便会出现如上的崩溃。

C++惯用法之RAII思想: 资源管理-CSDN博客

        既然已经有了RAII思想了,那必然是可以通过该思想来解决忘记join或detach导致崩溃的问题。所以std::jthread应运而生。当然std::jthread不止于此。

2.std::is_invocable_v

        std::is_invocable是C++17 中引入的一个类型特性(type trait),用于在编译时检查给定的类型是否可以被调用。换句话说,它可以用来检查一个类型(比如函数、函数对象、lambda 表达式等)是否可以作为函数调用操作符进行调用。

        具体来说,std::is_invocable模板接受一个函数类型和一组参数类型作为模板参数,并提供一个名为value的静态成员常量,用于表示给定的函数类型是否可以被调用。如果value为true,则表示给定的函数类型可以被调用,否则表示不可调用。

        示例如下:

#include <type_traits>  
  
struct Foo {  
    void operator()(int, int) {}  
};  
  
int main() {  
    // 检查函数是否可调用  
    static_assert(std::is_invocable_v<decltype(&main), int, char>);  // 错误,main不接受int和char作为参数  
    static_assert(std::is_invocable_v<decltype(main), void>);         // 正确,main不接受任何参数  
  
    // 检查函数对象是否可调用  
    static_assert(std::is_invocable_v<Foo, int, int>);               // 正确,Foo有一个接受两个int参数的调用操作符  
    static_assert(!std::is_invocable_v<Foo, double, double>);        // 错误,Foo没有接受两个double参数的调用操作符  
  
    // 检查lambda是否可调用  
    auto lambda = [](int a) { return a * 2; };  
    static_assert(std::is_invocable_v<decltype(lambda), int>);     // 正确,lambda接受一个int参数  
}

        在上面的示例中,std::is_invocable_v 是 std::is_invocable 的一个简化形式,它直接返回 true 或 false,而不是一个 std::true_type 或 std::false_type 的实例。

        这个特性在模板元编程和泛型编程中特别有用,因为它允许你在编译时基于可调用性来做出决策。

3.std::jthread

剖析其源码是了解其机理的最好方法,std::jthread的部分源码整理如下:

#if _HAS_CXX20
class jthread {
public:
    using id                 = thread::id;
    using native_handle_type = thread::native_handle_type;

    jthread() noexcept : _Impl{}, _Ssource{nostopstate} {}

    template <class _Fn, class... _Args, enable_if_t<!is_same_v<remove_cvref_t<_Fn>, jthread>, int> = 0>
    _NODISCARD_CTOR explicit jthread(_Fn&& _Fx, _Args&&... _Ax) {
        if constexpr (is_invocable_v<decay_t<_Fn>, stop_token, decay_t<_Args>...>) {
            _Impl._Start(_STD forward<_Fn>(_Fx), _Ssource.get_token(), _STD forward<_Args>(_Ax)...);
        } else {
            _Impl._Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
        }
    }

    ~jthread() {
        _Try_cancel_and_join();
    }

    jthread(const jthread&)     = delete;
    jthread(jthread&&) noexcept = default;
    jthread& operator=(const jthread&) = delete;

    jthread& operator=(jthread&& _Other) noexcept {
        // note: the standard specifically disallows making self-move-assignment a no-op here
        // N4861 [thread.jthread.cons]/13
        // Effects: If joinable() is true, calls request_stop() and then join(). Assigns the state
        // of x to *this and sets x to a default constructed state.
        _Try_cancel_and_join();
        _Impl    = _STD move(_Other._Impl);
        _Ssource = _STD move(_Other._Ssource);
        return *this;
    }

    void swap(jthread& _Other) noexcept {
        _Impl.swap(_Other._Impl);
        _Ssource.swap(_Other._Ssource);
    }

    _NODISCARD bool joinable() const noexcept {
        return _Impl.joinable();
    }

    void join() {
        _Impl.join();
    }

    void detach() {
        _Impl.detach();
    }

    _NODISCARD id get_id() const noexcept {
        return _Impl.get_id();
    }

    _NODISCARD stop_source get_stop_source() noexcept {
        return _Ssource;
    }

    _NODISCARD stop_token get_stop_token() const noexcept {
        return _Ssource.get_token();
    }

    bool request_stop() noexcept {
        return _Ssource.request_stop();
    }

    friend void swap(jthread& _Lhs, jthread& _Rhs) noexcept {
        _Lhs.swap(_Rhs);
    }

    _NODISCARD static unsigned int hardware_concurrency() noexcept {
        return thread::hardware_concurrency();
    }

private:
    void _Try_cancel_and_join() noexcept {
        if (_Impl.joinable()) {
            _Ssource.request_stop();
            _Impl.join();
        }
    }

    thread _Impl;
    stop_source _Ssource;
};
#endif // _HAS_CXX20

由以上代码可知:

1. 关注其构造函数:jthread不存在拷贝构造函数和拷贝赋值,存在移动构造函数和移动赋值运算符,即jthread不可拷贝但是可以转移。

2. 关注其成员变量_Impl为std::thread类型,即std::jthread采用RAII思想,在构造函数内构造std::thread,但是在其析构函数内判断是否为joinable状态,若其为joinable状态则调用std::thread的join函数,致使std::thread在析构时恒为非joinable,不会触发崩溃。关于此部分功能不再赘述,完全为std::thread的套壳。

3. 关注其成员变量_Ssource为std::stop_source类型,std::stop_source内维护stop_source的状态,其状态为std::_Stop_state,而std::_Stop_state实则是原子变量,通过判断该原子变量的值来处理线程的外部请求中断。

3.1.构造函数

先看一下源码:

template <class _Fn, class... _Args, enable_if_t<!is_same_v<remove_cvref_t<_Fn>, jthread>, int> = 0>
    _NODISCARD_CTOR explicit jthread(_Fn&& _Fx, _Args&&... _Ax) {
        if constexpr (is_invocable_v<decay_t<_Fn>, stop_token, decay_t<_Args>...>) {
            _Impl._Start(_STD forward<_Fn>(_Fx), _Ssource.get_token(), _STD forward<_Args>(_Ax)...);
        } else {
            _Impl._Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
        }
    }
...

从上面代码可以看出std::jthread的构造函数分为两种情况:

1)用std::is_invocable_v判断可调用对象_Fn的首个参数为std::stop_token,通过_Ssource.get_token()获取自身的std::stop_token传入_Impl._Start函数中,最终传入_Fn当中;之前我一直没有看懂,然后去看std::jthead的源码才恍然大悟。这种情况,那么线程就可以这样定义:

#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); // 打印 5 6 7 8... 约 3 秒
    std::this_thread::sleep_for(3s);
    // jthread 的析构函数调用 request_stop() 和 join()。
}

上面的std::jthread构造传入的 f 满足std::is_invocable,于是进入_Impl._Start(_STD forward<_Fn>(_Fx), _Ssource.get_token(), _STD forward<_Args>(_Ax)...); 开启线程。

2)和上面相反的可调用对象( 普通函数、类成员函数、仿函数、lambda函数等等) 首个参数不是std::stop_token,这种情况非常普通,用的也比较多,如下面示例:

#include<thread>

void func(int i,std::jthread& th){
    while (th.get_stop_token().stop_requested()) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

void main() {

    std::jthread t1;
    t1 = std::jthread(func, 12,std::ref(t1));

    // 终止线程的运行
    t1.request_stop();
}

        线程函数func检查线程对象的令牌状态,主线程通过改变线程对象的令牌状态,来终止子线程的任务。

        std::jthread的析构函数调用了join(),所以这里我们不需要显示调用join()来等待。

3.2.std::jthread无需join/detach使用实例

std::jthread j{[]{
    std::cout << "sub jthread execuate, thread id" << std::this_thread::get_id();
  }};

  //正常输出,并未崩溃,某次执行结果如下
  //sub jthread execuate, thread id35732

3.3.std::jthread处理外部请求中断实

std::jthread提供三个接口并配合std::stop_token的stop_requested来实现外部请求中段处理。

//std::jthread
_NODISCARD stop_source get_stop_source() noexcept {
        return _Ssource;
    }

 _NODISCARD stop_token get_stop_token() const noexcept {
     return _Ssource.get_token();
 }

 bool request_stop() noexcept {
     return _Ssource.request_stop();
 }

//stop token
 _NODISCARD bool stop_requested() const noexcept {
     const auto _Local = _State;
     return _Local != nullptr && _Local->_Stop_requested();
 }

3.4.处理中断请求示例代码

void using_jthread_with_stop_token()
{
  std::jthread j{ [](std::stop_token token) {
    std::cout << "sub jthread execate, thread id" << std::this_thread::get_id()<<"\n";
    for (int i =0; i< 20; i++)
    {
      std::cout<<"sub jthread "<<i<<"\n";
      std::this_thread::sleep_for(std::chrono::seconds(1));
      if (token.stop_requested())
      {
        std::cout<<"exit sub jthread " << std::this_thread::get_id() << "\n";
        return;
      }
    }
  } };

  std::cout << "running main thread "<<std::this_thread::get_id()<<"\n";
  std::this_thread::sleep_for(std::chrono::seconds(5));
  j.request_stop();
  std::cout << "exit main thread " << std::this_thread::get_id() << "\n";
}

//output result:
/*
running main thread 34396
sub jthread execate, thread id21536
sub jthread 0
sub jthread 1
sub jthread 2
sub jthread 3
sub jthread 4
exit main thread 34396
exit sub jthread 21536
*/

由源码可知,除直接使用std::jthread对象请求中断外,还可以使用source,即通过std::jthread的get_stop_source接口获得其source,而后通过source来请求中断,示例代码如下:

void using_jthread_with_source_request_stop()
{
  std::jthread j{ [](std::stop_token token) {
    std::cout << "sub jthread execuate, thread id " << std::this_thread::get_id() << "\n";
    for (int i = 0; i < 20; i++)
    {
      std::cout << "sub jthread " << i << "\n";
      std::this_thread::sleep_for(std::chrono::seconds(1));
      if (token.stop_requested())
      {
        std::cout << "exit sub jthread " << std::this_thread::get_id() << "\n";
        return;
      }
    }
  } };

  
  auto source = j.get_stop_source();
  std::thread  t{[](std::stop_source source){
    std::cout << "running t thread " << std::this_thread::get_id() << "\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    source.request_stop();
  
  },source};
  t.join();
  std::cout << "t thread  joined" << "\n";
}
//output result:
/*
running t thread 20280
sub jthread execuate, thread id 4164
sub jthread 0
sub jthread 1
sub jthread 2
sub jthread 3
sub jthread 4
t thread  joined
exit sub jthread 4164
*/

4.特性

        std::jthread 是 C++20 中引入的一个新特性,它是 std::thread 的一个扩展,专为与 C++ 的执行策略(execution policies)和并行算法(parallel algorithms)配合使用而设计。std::jthread 的主要目的是提供一种机制,使得线程可以自动地与执行策略一起工作,并在适当的时候进行调度和管理。
      std::jthread 提供了以下特性:
      自动管理:std::jthread 在其析构时会自动调用 std::jthread::join(),从而避免了忘记调用 join() 或 detach() 而导致的资源泄露或程序行为不确定的问题。
      异常传播:如果 std::jthread 运行的函数抛出了异常,并且这个异常没有被捕获,那么 std::jthread 的析构函数会重新抛出这个异常。这使得在 std::jthread 对象的生命周期结束时,能够更容易地诊断和处理异常。
      执行策略集成:std::jthread 可以与 C++ 的执行策略(如 std::execution::par、std::execution::seq 等)一起使用,以控制并行算法的执行方式。这使得线程能够更容易地集成到并行计算框架中。
     合作式取消:std::jthread 支持一种称为“合作式取消”的机制,允许在适当的时候请求线程停止执行。虽然这并不能强制线程立即停止,但它提供了一种机制,使得线程可以在检查取消请求时优雅地停止。

5.总结

1)std::jthread析构自动汇合,不回崩溃。
2)std::jthread支持joinable、join、detach、get_id、hardware_concurrency等原生std::thread的接口,故std::jthread可以无缝替换std::thread。
3)std::jthread支持外部请求中断,无需再向使用std::thread那样,提供一个标志位来作为线程启停的标志。

参考:

std::jthread - cppreference.com

std::thread - cppreference.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1532114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯刷题(十二)

1.答疑 代码 n int(input()) L [] for i in range(n):a,b,c map(int,input().split())A ab # 进入和答疑时间B abc # 个人总用时L.append([A,B]) L.sort(keylambda x:x[1]) # 个人总用时短的优先 ans tmp 0 # ans为发消息时刻&#xff0c;tmp为前一个人的总用时 for i …

【位运算】【 数学】【 哈希映射】2857. 统计距离为 k 的点对

本文涉及知识点 位运算 数学 哈希映射 LeetCode 2857. 统计距离为 k 的点对 给你一个 二维 整数数组 coordinates 和一个整数 k &#xff0c;其中 coordinates[i] [xi, yi] 是第 i 个点在二维平面里的坐标。 我们定义两个点 (x1, y1) 和 (x2, y2) 的 距离 为 (x1 XOR x2) …

数学建模-邢台学院

文章目录 1、随机抽取的号码在总体的排序2、两端间隔对称模型 1、随机抽取的号码在总体的排序 10个号码从小到大重新排列 [ x 0 , x ] [x_0, x] [x0​,x] 区间内全部整数值 ~ 总体 x 1 , x 2 , … , x 10 总体的一个样本 x_1, x_2, … , x_{10} ~ 总体的一个样本 x1​,x2​,……

【Leetcode每日一题】 递归 - 反转链表(难度⭐)(36)

1. 题目解析 题目链接&#xff1a;206. 反转链表 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 一、递归函数的核心任务 递归函数的主要职责是接受一个链表的头指针&#xff0c;并返回该链表逆序后的新头结点。递归…

两台电脑简单的通信过程详解(局域网,同网段)

来源&#xff1a; https://www.bilibili.com/video/BV1BA411373b/ 一、原理 描述过程&#xff1a;分别以PC1、PC2、PC2、PC1的角度 二、eNSP测试 1.连接设备 2.查看PC1情况 3.打开抓包后&#xff0c;再ping一下PC2 4.PC1发送ARP报文 broadcast 意思为广播(IP都是f,意为255…

return code 1 from org.apache.hadoop.hive.ql.ddl.DDLTask

Bug信息 Error: Error while compiling statement: FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.ddl.DDLTask (state=08S01,code=1)Bug产生的代码 修复hive表分区: msck repair table xxxBug原因排查 分区数量过大 这个是网上查看的说如果一次…

2024.3.21 如何将idea的注释设置为在首字母前开始而不是句首

2024.3.21 如何将idea的注释设置为在首字母前开始而不是句首 两种写法的差异 修改办法 将右下角的勾去掉即可。

redis关联和非关联

1.1.2.关联和非关联 传统数据库的表与表之间往往存在关联&#xff0c;例如外键&#xff1a; 而非关系型数据库不存在关联关系&#xff0c;要维护关系要么靠代码中的业务逻辑&#xff0c;要么靠数据之间的耦合&#xff1a; {id: 1,name: "张三",orders: [{id: 1,ite…

手拉手整合Springboot3+RocketMQ2.3

RocketMQ 基本概念 消息模型Message Model RocketMQ 主要由 Producer、Broker、Consumer 三部分组成&#xff0c;其中 Producer 负责生产消息&#xff0c;Consumer 负责消费消息&#xff0c;Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器&#xff0c;每个 Bro…

【报错】使用gradio渲染html页面无法加载本地图片

【报错】使用gradio渲染html页面无法加载本地图片 【报错】使用gradio渲染html页面无法加载本地图片[HTML] how to load local image by html output #884成功解决 【报错】使用gradio渲染html页面无法加载本地图片 在使用gradio框架渲染html页面&#xff0c;使用绝对路径&quo…

获取cookie

在Servlet9里设置cookie 在Servlet10里进行获取 访问Servlet9.do&#xff0c;再访问Servlet10.do

图书馆RFID(射频识别)数据模型压缩/解压缩算法实现小工具

1. 前言 最近闲来无聊&#xff0c;看了一下《图书馆射频识别数据模型第1部分&#xff1a;数据元素的设置及应用规则》以及《图书馆射频识别数据模型第2部分&#xff1a;基于ISO/IEC 15962的数据元素编码方案》&#xff0c;决定根据上面的编码方法实现一下该算法&#xff0c;于…

PyTorch 深度学习(GPT 重译)(五)

十二、通过指标和增强改进训练 本章涵盖 定义和计算精确率、召回率以及真/假阳性/阴性 使用 F1 分数与其他质量指标 平衡和增强数据以减少过拟合 使用 TensorBoard 绘制质量指标图 上一章的结束让我们陷入了困境。虽然我们能够将深度学习项目的机制放置好&#xff0c;但实…

聚类算法之层次聚类(Hierarchical Clustering)

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 层次聚类是一种非常独特和强大的聚类方法&#xff0c;与众多其他的聚类技术相比&#xff0c;它不仅为数据集提供了一个划分&#xff0c;还给出了…

3d模型文件导入时没颜色---模大狮模型网

导入3D模型文件时没有颜色显示通常是由于软件在显示模型时未正确解释颜色信息。这可能是由于模型文件本身没有包含颜色信息&#xff0c;或者是软件在导入过程中未正确处理颜色数据所致。 以下是一些可能的解决方法&#xff1a; 检查3D模型文件&#xff1a;首先&#xff0c;确保…

网络简略总结

目录 一、三次握手 四次挥手 1、三次握手:为了建立长链接进行交互即建立一个会话,使用http/https协议 2、四次挥手是一个断开连接释放服务器资源的过程 3、如果已经建立了连接,但是客户端突然出现故障了怎么办? 4、谁可以中断连接?客户端还是服务端还是都可以? 5、…

Linux环境开发工具之vim

前言 上一期我们已经介绍了软件包管理器yum&#xff0c; 已经可以在linux上查找、安装、卸载软件了&#xff0c;本期我们来介绍一下文本编辑器vim。 本期内容介绍 什么是vim vim的常见的模式以及切换 vim命令模式常见的操作 vim底行模式常见的操作 解决普通用户无法执行sudo问…

5G智能网关助力工业铸造设备监测升级

随着物联网技术的迅猛发展和工业4.0浪潮的推进&#xff0c;传统工业正面临着严峻的转型升级压力。在这一背景下&#xff0c;铸造行业——这一典型的传统重工业领域&#xff0c;也必须积极探索借助5G、物联网、边缘计算等技术提升生产经营效率的新路径。 本文就基于佰马合作伙伴…

【晴问算法】提高篇—动态规划专题—最大连续子序列和

题目描述 输入描述 输出描述 输出一个整数&#xff0c;表示最大连续子序列和。 样例1 输入 6 -2 11 -4 13 -5 -2 输出 20 解释 连续子序列和的最大值为&#xff1a; #include<bits/stdc.h> using namespace std; const int MAXN 100; int dp[MAXN];//dp[i]表示以a[i]元…

酷开科技聚焦大屏端数据研究,构建酷开系统深度挖掘大屏商业价值

中国所有的彩色大屏中&#xff0c;智能电视规模已经过半&#xff0c;OTT平台的数据价值越发引起人们关注。作为OTT行业的头部代表&#xff0c;酷开科技一直聚焦大屏端数据研究&#xff0c;目前已经形成一套基于大屏指数的智慧营销体系&#xff0c;让OTT大屏的数字营销化水平实现…