有了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.mzph.cn/news/758607.shtml

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

相关文章

Redis实现高可用方案

文章目录 前言一、主从模式1.1 复制流程1.2 优缺点 二、哨兵模式2.1 介绍2.2 哨兵的工作模式 三、集群模式3.1 Cluster集群节点的通讯3.2 Hash Slot插槽算法3.3 Redis Cluster集群3.4 故障转移 前言 如果单机部署Redis服务的话&#xff0c;一旦Reids宕机&#xff0c;那么整个服…

英伟达 V100、A100/800、H100/800 GPU 对比

近期&#xff0c;不论是国外的 ChatGPT&#xff0c;还是国内诸多的大模型&#xff0c;让 AIGC 的市场一片爆火。而在 AIGC 的种种智能表现背后&#xff0c;均来自于堪称天文数字的算力支持。以 ChatGPT 为例&#xff0c;据微软高管透露&#xff0c;为 ChatGPT 提供算力支持的 A…

centos 环境部署

一、安装redis 1. 升级 GCC 最直接的解决方式是升级你的 GCC 编译器到支持 C11 标准的版本。CentOS 7 默认的 GCC 版本较旧&#xff0c;可能不支持 _Atomic。你可以通过以下步骤升级 GCC&#xff1a; 启用 CentOS 的 Software Collections (SCL) 仓库&#xff0c;该仓库提供了…

王老吉药业开拓数字经济“新蓝海”,成立数字经济研究所,科技赋能新品压片糖

3月12日&#xff0c;广州王老吉药业股份有限公司&#xff08;以下简称“王老吉药业”&#xff09;召开第十一届312感恩活动新闻发布会&#xff0c;宣告王老吉数字经济研究所成立&#xff0c;并发布王老吉压片糖新品。一系列重要重要举措&#xff0c;无一不标志着王老吉药业正以…

Java SE入门及基础(44)

目录 I / O流(上) 1. 什么是I / O流 过程分析 I / O的来源 Java 中的 I / O流 2. 字节流 OutputStream 常用方法 文件输出流 FileOutputStream 构造方法 示例 InputStream 常用方法 文件输入流 FileInputStream 构造方法 示例 综合练习 字节流应用场景 Java SE文…

自动化测试报告生成(Allure)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 之前尝试使用过testNG自带的测试报告、优化过reportNG的测试报告…

算法·动态规划Dynamic Programming

很多人听到动态规划或者什么dp数组了&#xff0c;或者是做到一道关于动态规划的题目时&#xff0c;就会有一种他很难且不好解决的恐惧心理&#xff0c;但是如果我们从基础的题目开始深入挖掘动规思想&#xff0c;在后边遇到动态规划的难题时就迎难而解了。  其实不然&#xff…

linux:线程互斥

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、线程互斥问题解释互斥量的接口 二、加锁的原理三、 死锁死锁四个必要条件避免死锁 总结 前言 本文是对于线程互斥的知识总结 一、线程互斥 问题 我们先看下面…

财报解读:出海“窗口期”再现,汇量科技保驾护航的底气源于什么

大数据时代&#xff0c;每个人的喜好都被精准捕捉。购物APP、购物网站们&#xff0c;都仿佛一位贴心的时尚顾问。而这源于个性化广告经过深度学习和智能算法得来的结果。 随着广告市场的竞争愈演愈烈&#xff0c;广告主们需要更为精准、高效的个性化投放。近日&#xff0c;深耕…

基于SSM的宿舍管理系统的设计与实现(JSP,MySQL)

摘 要 随着社会发展、信息技术的普及&#xff0c;人们日常管理工作也发生了巨大的变化。信息化技术之渗透各行业的方方面面。学生宿舍管理作为校园管理工作的重要一环&#xff0c;不仅关系到学生自身的确切利益&#xff0c;同时也是对校园管理工作重大考验。近来年由于在校学生…

leetcode代码记录(移除链表元素

目录 1. 题目&#xff1a;2. 我的代码&#xff1a;小结&#xff1a; 1. 题目&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head […

Flutter开发进阶之瞧瞧Widget

Flutter开发进阶之瞧瞧Widget 在Flutter开发中,WIdget是构建界面的基本单元;Widget是不可变的,意味着一旦创建如果需要改变UI就需要重新创建一个新的Widget;在实际开发中,Widget通常由一个个Widget组合而成,从而形成嵌套的树形结构,复杂的UI就是由这一个个Widget构建而…

【C语言】—— 指针三 : 参透数组传参的本质

【C语言】—— 指针三 &#xff1a; 参透数组传参的本质 一、数组名的理解二、使用指针访问数组2.1、指针访问数组2.2、[ ] 的深入理解2.3、数组与指针的区别 三、一维数组的传参本质四、数组指针变量4.1、数组指针变量是什么4.2、 数组指针的初始化 五、二维数组传参的本质 一…

简单了解多线程

并发和并行 并发&#xff1a; 在同一时刻&#xff0c;多个指令在单一CPU上交替指向 并行&#xff1a;在同一时刻&#xff0c;多个指令在多个CPU上同时执行 2核4线程&#xff0c;4核8线程&#xff0c;8核16线程&#xff0c;16核32线程 基础实现线程的方式 Thread :继承类 &…

多人命题系统|基于SSM框架+ Mysql+Java+ B/S结构的多人命题系统设计与实现(可运行源码+数据库+设计文档)

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

13年资深测试,性能测试常见指标分析总结,看这篇就够了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、什么是性能测试…

Kotlin runBlocking CoroutineScope synchronized简单死锁场景

Kotlin runBlocking CoroutineScope synchronized简单死锁场景 import kotlinx.coroutines.*fun main(args: Array<String>) {runBlocking {val lock1 Any()val lock2 Any()CoroutineScope(Dispatchers.IO).launch {repeat(10) {println("A-$it 申请 lock1...&quo…

Http 超文本传输协议基本概念学习摘录

目录 HTTP协议 超文本传输协议 HyperText超文本 HTML超文本标记语言 HTTP协议原理 请求发送 服务器处理 响应发送 连接关闭或保持 HTTP协议版本 HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/3 HTTP请求方法 GET POST PUT DELETE HEAD OPTIONS HTTP请求头字…

JVM学习-类加载

目录 1.类文件结构 2.类加载器 3.类加载的三个阶段 3.1加载 3.2链接 3.2.1验证 3.2.2准备阶段 3.2.3解析阶段 3.3初始化 4.拓展&#xff1a;反射 4.1获取类对象 4.2创建实例 4.3获取方法 4.4方法调用 1.类文件结构 2.类加载器 类加载器用来将类文件的二进制字节码加载到JV…

猜数字游戏有三变(Java篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…