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

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.mfbz.cn/a/472973.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高校二手交易平台|基于JSP(Java脚本页面)+ Mysql+Java+ B/S结构的高校二手交易平台设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

SSA优化朴素贝叶斯分类预测(matlab代码)

SSA-朴素贝叶斯分类预测matlab代码 麻雀搜索算法(Sparrow Search Algorithm, SSA)是一种新型的群智能优化算法&#xff0c;在2020年提出&#xff0c;主要是受麻雀的觅食行为和反捕食行为的启发。 数据为Excel分类数据集数据。 数据集划分为训练集、验证集、测试集,比例为8&a…

Java毕业设计 基于SSM网上花店 订花系统 在线花店

Java毕业设计 基于SSM网上花店 订花系统 在线花店 SSM jsp 网上花店 订花系统 在线花店 功能介绍 前端用户&#xff1a;首页 图片轮播 搜索 登录 注册 鲜花分类 分类显示 折扣花束 热销花束 花卉列表 花卉详情 收藏 加入购物车 评价 活动公告 公告详情 买家留言 我的购物车 购…

Linux编程4.9 网络编程-建立连接

1、TCP的连接与断开 三次握手与四次挥手 2、服务器端并发性处理 2.1 多进程模型 一个父进程&#xff0c;多个子进程父进程负责等待并接受客户端连接子进程: 完成通信&#xff0c;接受一个客户端连接&#xff0c;就创建一个子进程用于通信。 2.2 多线程模型 多线程服务器是…

5 Redis主从集群

文章目录 Redis主从集群1.1主从集群搭建1.1.1 伪集群搭建与配置1.1.2 分级管理1.1.3 容灾冷处理 1.2主从复制原理1.2.1 主从复制过程1.2.2 数据同步演变过程 2.1 哨兵机制实现2.1.1 简介2.2.2 Redis 高可用集群搭建2.2.3 Redis 高可用集群的启动2.2.4 Sentinel 优化配置 3.1 哨…

XshellPlus V7.0.0033r 绿化便携版

Xshell&#xff0c;最好用的Linux远程连接工具&#xff0c;最强大的SSH终端管理器、SSH远程主机连接客户端 。Xshell&#xff0c;轻松管理远程服务器&#xff0c;会话管理器&#xff0c;支持多选项卡管理主机&#xff0c;支持远程协议Telnet、Rlogin、SSH/SSH PKCS&#xff03;…

Springboot+vue的船舶维保管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的船舶维保管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09…

备战秋招(coding篇)

其中coding题目来源于师兄面试经验 1、链表的结构体反转链表 本质上就是一个构造函数 struct ListNode{int val_;ListNode* next_;ListNode() : val_(0), next_(NULL) {}ListNode(int x) : val_(x), next_(NULL) {}ListNode(int x, ListNode* next) : val_(x), next_(next) …

我的保研材料全部损坏了!这个压缩包文件格式未知或数据已经被损坏不可预料的压缩文件末端

求助各位友友&#xff0c;我的保研材料全部没了&#xff01; 之前为了清理D盘&#xff0c;把之前保研期间准备的几个G的材料全部压缩放在了U盘&#xff0c;但是现在却损坏打不开了&#xff0c;之前为了省事也没有添加过“恢复记录”&#xff01;&#xff01;&#xff01; 先声…

什么是状态压缩DP???

1. 引言 相信大家已经对普通的01背包或者其他背包问题已经很熟练了&#xff0c;但是有时候我们去解决NP问题&#xff08;指数级别的复杂度&#xff0c;比如N&#xff01;&#xff09;&#xff0c;时间复杂度就会非常之大 所以&#xff0c;这个时候我们需要寻找更加优化的方法…

数据结构算法 - 数组 Array

一、概念 结构是一种线性表&#xff08;元素排列成直线的结构&#xff09;&#xff0c;创建数组会开辟一块连续的内存空间&#xff0c;长度固定无法更改&#xff0c;元素可以重复且只能是同一种类型&#xff08;Object类型数组除外&#xff09;。优点查询快&#xff1a;由于元…

对话奇酷网络董事长吴渔夫: 迟到的游戏公司会被AI浪潮卷入海底

“ 迟到的游戏公司会被无形的 AI 浪潮卷入海底。” 整理 | 梦婕 编辑 | 云舒 出品&#xff5c;极新 2024年3月4日&#xff0c;在极新与吴渔夫的对话中&#xff0c;吴渔夫多次呼吁“全力拥抱AI”。在这场AI浪潮中&#xff0c;作为中国网游的先锋&#xff0c;他带着 25 年“中…

外包干了1个月,技术明显进步。。。

我是一名大专生&#xff0c;自19年通过校招进入湖南某软件公司以来&#xff0c;便扎根于功能测试岗位&#xff0c;一晃便是近四年的光阴。今年8月&#xff0c;我如梦初醒&#xff0c;意识到长时间待在舒适的环境中&#xff0c;已让我变得不思进取&#xff0c;技术停滞不前。更令…

GraalVM:新一代跨语言虚拟机的崛起

有朋友后台私信让聊聊GraalVM&#xff0c;目前这玩意我只自己尝鲜搞过&#xff0c;没搞过线上&#xff0c;后续有机会会补充个实践 其实&#xff0c;随着信息技术的快速发展&#xff0c;编程语言多样化已成为软件开发领域的常态。为了满足不同编程语言间的互操作性和性能需求&a…

Java特性之设计模式【组合模式】

一、组合模式 概述 组合模式&#xff08;Composite Pattern&#xff09;&#xff0c;又叫部分整体模式&#xff0c;是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次。这种类型的设计模式属于结构型模式&#x…

隐私计算实训营学习一:数据可信流通,从运维信任到技术信任

文章目录 一、数据可信流通二、数据可信流通的技术信任基础三、技术信任开启数据密态时代&#xff0c;保障广域数据可信流通 一、数据可信流通 可信数据流通体系&#xff1a;数据二十条第一次明确提出可信流通&#xff0c;建立数据来源可确认、使用范围可界定、流通过程可追溯…

金融知识分享系列之:支撑阻力

金融知识分享系列之&#xff1a;支撑阻力 一、支撑阻力原理二、支撑阻力作用1.识别市场资金的预期2.作为入场和平仓的重要参考 三、寻找支撑阻力四、延伸思考五、支撑阻力总结 一、支撑阻力原理 支撑阻力核心要素&#xff1a; 锚定效应订单驱动 支撑阻力原理&#xff1a; 市…

在DevEco Studio中第一次使用网络图片不显示问题

当我们新建项目 第一次使用网络图片 没有显示时 加这段代码就可以了 如果刷新图片还是没有显示 就重启编辑器。 "requestPermissions": [{"name": "ohos.permission.INTERNET"}],

>>Vue3+pinia+echarts等实现疫情可视化大图

一.>>前言 1.这个项目是在小满实战篇可视化&#xff08;第九章-饼图&#xff09;_哔哩哔哩_bilibili 这一系列课程为基础来做的&#xff0c;真的很感谢小满老师&#xff0c;讲的内容干货满满&#xff0c;暂时解决了手上没有项目的难题。大家可以去观摩一下他的优质课程。…

WT32-ETH02 plus 串口转以太网开发,WT32-ETH01网关开发板升级款!

广受欢迎的WT32-ETH01网关开发板迎来了升级。 就是这款启明云端新推出的嵌入式串口转以太网开发板——WT32-ETH02 plus。应广大客户的需求&#xff0c;在WT32-ETH01的基础上增加了POE供电&#xff0c;可广泛应用于智能家居和网关等应用。开发板搭载2.4GHz Wi-Fi和蓝牙双模的SO…