C++20中的jthread

一、多线程开发

c++11以前,是不包含线程库的,开发多线程,必须使用OS系统自带的线程API,这就导致了很多问题,最主要的是,跨平台的开发,一般要保持几个主流应用的库的代码支持,特别是对Win平台和Linux平台的两个线程库的支持,否则,就无法实现简单的跨平台的功能。而两个平台的多线程的使用差异和一些细节不同的,比如事件Event在Linux上没有。而条件变量在Win平台上没有,就更容易使一些应用场景的开发的复杂性大幅提高。
而到了c++11以后,提供了std::thread这个多线程的支持,可以说大幅的降低了跨平台开发的复杂性。
但是在实际的应用过程中,又遇到了一些新的需求变化,比如应用的方便性和多线程运行过程中的可操作性及可见性上,都需要提供新的接口。按照c++设计者们的一贯作风,他们不会在基础的std::thread本身进行修改,那么,他们就对这个类进行了再封装,增加了上述的功能,然后提出了std::jthread这个类。

二、jthread的功能和实现

一般来说,接口越丰富,那么这个类功能越强大,但是反过来,缺点就是不容易掌握并且容易忘记调用,在使用std::thread时,有没有忘记调用join()或者detach()的时候?自动调用好不好?线程函数运行过程中,可不可以可以动态请求交互一下,而不是靠各种变量来预先进行控制。对,在std::jthread上都实现了。
看一看标准文档是如何定义的:

std::jthread::jthread
jthread() noexcept;                              (1)	(C++20)
jthread( jthread&& other ) noexcept;             (2)	(C++20)
template< class Function, class... Args >
explicit jthread( Function&& f, Args&&... args );(3)	(C++20)
jthread( const jthread& ) = delete;              (4)	(C++20)

第一个表示创建新的jthread对象;第二个表示移动语义,既然移动了,那么原来线程停止执行线程,由新线程执行;第三个表示线程函数f接受 std::stop_token 作为其首参数,则新线程开始执行:

std::invoke(decay_copy(std::forward<Function>(f)),get_stop_token(),decay_copy(std::forward<Args>(args))...);

;否则它开始执行

std::invoke(decay_copy(std::forward<Function>(f)),decay_copy(std::forward<Args>(args))...);

任一情况下, decay_copy 定义为

template <class T>
std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }

除了在调用方的语境中执行 decay_copy ,故而在求值和移动/构造参数期间抛出的任何异常都在当前线程抛出,而不会开始新线程。
构造函数的完成同步于(按 std::memory_order 中定义) f 的副本在新线程上调用的开始。
若 std::remove_cvref_t 与 std::jthread 为相同类型则此构造函数不参与重载决议。
最后一个表示不可复制,也就是说只能它自己执行线程,没有任何其它一个std::jthread对象可以表示同一线程。

std::jthread的成员函数里,可以看支持查看硬件并发数的函数,交换线程对象的函数,协作请示的函数等,更多的详情可以参看一下:
https://zh.cppreference.com/w/cpp/thread/jthread/jthread
https://zh.cppreference.com/w/cpp/header/stop_token

三、应用

线程的中断和取消是需要高度引起注意的,一不小心,强行KILL的线程中的各种资源或者句柄就没有回收,导致各种泄露。这也是在多线程编程中,普遍有一个要求,就是让线程自己执行完毕退出,而为了这个要求,就需要牺牲一下控制的效率。
当然,先从std::thread的多线程编程的例子看:

#include <Windows.h>
#include <iostream>
#include <thread>
#include <chrono>bool quit = false;void SleepTime(int sec)
{std::this_thread::sleep_for(std::chrono::seconds(sec));
}
void ThreadWorker()
{//work work workstd::cout << "thread start,working! " << std::endl;
}void MainWorker()
{std::cout << "main thread start,working! " << std::endl;
}
int main()
{std::thread t = std::thread([&]() {while (!quit){ThreadWorker();SleepTime(2);}});//Sleep(3);MainWorker();t.join();//t.detach();
}

这个DEMO非常简单,在主线程中启动了一个子线程,需要注意一下注释的延时的情况,主要是起一个先后交替的条件。主线程在执行完成自己的工作后,就会join等待子线程的完成,像上面这种情况,就死循环了,不会停止。如果需要停止只能使用设置quit为True或者强制的命令等不友好的手段了。首先看一下,换成jthread会是啥样?其它代码都没有改变,只是修改了下面的代码:

std::jthread t = std::jthread([&]() {while (!quit){ThreadWorker();SleepTime(2);}
});

需要包含的jthread等的头文件可以去github或者相关的网站上下载,这里只提供一处,并对此表示感谢:
https://github.com/josuttis/jthread/tree/master/source
上面的两处代码的运行结果是完全一样的,没有什么不同。那用这个类的优势呢?别急,把main函数改一下:

int main()
{std::jthread t = std::jthread([&]() {while (!quit){ThreadWorker();SleepTime(2);}});Sleep(3);MainWorker();//t.join();//t.detach();
}

把最后一行注释,再编译运行,仍然没有啥问题,有一点不同了吧。这可以避免粗心大意的同学出现意外错误吧。再看一下,如果设置quit为true,那么线程会怎么办?没啥大问题,到了判断就退出了,可是如果有多个线程在执行,是不是要设置多个类似的变量呢?答案是肯定的。这时候儿看看jthread的协作中断:

int main()
{std::jthread t = std::jthread([&]() {while (!quit){ThreadWorker();SleepTime(2);}});Sleep(3);MainWorker();t.request_stop();//简单增加一行代码
}

编译运行,发现没啥反应,好,再修改一下线程函数:

int main()
{std::jthread t = std::jthread([&](std::stop_token st) {while (/*!quit*/!st.stop_requested()){ThreadWorker();SleepTime(2);}});Sleep(3);MainWorker();//getchar();t.request_stop();//t.join();//t.detach();getchar();
}

这个时候儿发现没啥问题了,注意上面getchar()函数的位置,如果在后面,则执行一次就退出了。如果在前面,就继续不断执行。把t.request_stop();这一行也注释,发现程序仍然和不注释一样,看来这个jthread里做了什么运作,看一下它的析构函数:

inline jthread::~jthread() {if (joinable()) {   // if not joined/detached, signal stop and wait for end:request_stop();join();}
}

明白了吧。至于为什么不用detach(),可能是大牛们觉得join()会更安全一些吧。毕竟等待完成后,所有的对象都被回收了。下面再看官网提供的一个例子:

class foo
{
public:void bar(){for (int i = 0; i < 5; ++i) {std::cout << "Thread 3 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}}int n = 0;
};
int main()
{foo f;std::jthread t3(&foo::bar, &f);std::jthread t = std::jthread([&](std::stop_token st) {while (/*!quit*/!st.stop_requested()){ThreadWorker();SleepTime(2);}});std::cout << t.hardware_concurrency() << std::endl;Sleep(3);MainWorker();getchar();t.request_stop();//t.join();//t.detach();
}

执行一下,就可以看出上面提到的decay_copy这个模板函数的使用,没啥不容易理解的。

4、总结

其实还有几个特点值得说说,但不准备再讲了,虽然已经投票完成确定了c++20的标准,但毕竟还没有正式增加进去,大家有兴趣可以看看源码。整体来看,c++应用还是越来越方便,这对c++来说,肯定是个好事情。现在就希望>VS2019或者>GCC11中,尽快完全支持。

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

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

相关文章

关于缓存的一些问题

关于缓存的一些问题 1、缓存穿透 什么是缓存穿透&#xff1a; 缓存穿透指的是在使用缓存系统的过程中&#xff0c;对于不存在的数据不断地进行查询请求&#xff0c;导致这些请求都无法从缓存中获取到数据&#xff0c;最终达到了绕过缓存的目的&#xff0c;直接访问后端数据源…

C语言-文件操作函数基础+进阶标准输入流输出流

学习的流程 ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————…

RedisDesktopManager 安装

简介&#xff1a;安装redis可视化工具 一、下载压缩包 Redis 可视化工具 链接&#xff1a;https://pan.baidu.com/s/1P2oZx9UpQbXDsxJ3GPUeOQ 提取码&#xff1a;6rft Redis 命令窗口版本 链接&#xff1a;https://pan.baidu.com/s/1mIuxCEWwD__aoqp1Cx8MFQ 提取码&#xf…

Lucene及概念介绍

Lucene及概念介绍 基础概念倒排索引索引合并分析查询语句的构成 基础概念 Document&#xff1a;我们一次查询或更新的载体&#xff0c;对比于实体类 Field&#xff1a;字段&#xff0c;是key-value格式的数据&#xff0c;对比实体类的字段 Item&#xff1a;一个单词&#xff0…

如何做一个知识博主? 行动

任何事情都需要经过两次创造 提前做一个预演&#xff0c;计划 【以一个计划开始】 你的解决方案究竟将怎样应用? 要根据目标想到所有的关键因素并列出所有的步骤和所有重要的细节。 需要想清楚这个事情成事最核心的胜负手关键因素&#xff1f; 【抓关键】 涨粉&#xff1a…

Decoupled Multimodal Distilling for Emotion Recognition 论文阅读

Decoupled Multimodal Distilling for Emotion Recognition 论文阅读 Abstract1. Introduction2. Related Works2.1. Multimodal emotion recognition2.2. Knowledge distillation3. The Proposed Method3.1. Multimodal feature decoupling3.2. GD with Decoupled Multimodal …

简单易懂的SQL添加数据教程

1. 引言&#xff1a; SQL&#xff08;Structured Query Language&#xff09;是一种处理关系型数据库的标准语言&#xff0c;包括插入&#xff08;INSERT)、更新&#xff08;UPDATE&#xff09;、删除&#xff08;DELETE&#xff09;等操作。在数据库操作中&#xff0c;添加数…

基于muduo网络库实现的集群聊天服务器

目录 项目内容开发环境安装说明技术介绍项目目录数据库设计项目介绍启动服务器启动客户端注册账号登录成功一对一聊天业务创建群聊业务加入群聊业务群聊业务添加好友业务离线消息存储业务 特殊说明 &#xff01;&#xff01;&#xff01;项目是照着腾讯课堂施磊老师的视频学习&…

NC20128 不重复数字

题目描述 给出N个数&#xff0c;要求把其中重复的去掉&#xff0c;只保留第一次出现的数。 例如&#xff0c;给出的数为1 2 18 3 3 19 2 3 6 5 4&#xff0c;其中2和3有重复&#xff0c;去除后的结果为1 2 18 3 19 6 5 4。 输入描述: 输入第一行为正整数T&#xff0c;表示…

04---java面试八股文——spring-----注解-------10题

31、ComponentScan注解的作用 ComponentScan 注解用于指定 Spring 容器在启动时要扫描的基础包路径&#xff0c;以及要扫描的包路径下应该被自动注册为 Spring bean 的类。 具体来说&#xff0c;ComponentScan 注解的作用有以下几个方面&#xff1a; 组件扫描&#xff1a;Comp…

docker部署DOS游戏

下载镜像 docker pull registry.cn-beijing.aliyuncs.com/wuxingge123/dosgame-web-docker:latestdocker-compose部署 vim docker-compose.yml version: 3 services:dosgame:container_name: dosgameimage: registry.cn-beijing.aliyuncs.com/wuxingge123/dosgame-web-docke…

How to install JDK on mac

文章目录 1. Install JDK on mac2. zshenv, zshrc, zprofile3. 查看java环境变量配置 1. Install JDK on mac Installation of the JDK on macOS 2. zshenv, zshrc, zprofile How Do Zsh Configuration Files Work? 3. 查看java环境变量配置 open Terminal&#xff0c;cd…

02-JDK新特性-Lambda表达式

JDK新特性 Lambda表达式 什么是Lambda表达式 Lambda表达式是一个匿名代码块&#xff0c;用于简单的传递一段代码片段。 Lambda表达式标准格式 格式&#xff1a;(形式参数) -> {代码块} 形式参数 如果有多个参数&#xff0c;参数只见用逗号隔开&#xff1b;如果没有&…

普通数据库索引与搜索引擎的索引有何区别

普通数据库索引&#xff0c;如关系型数据库&#xff08;RDBMS&#xff09;中的B树&#xff08;B-tree&#xff09;或哈希索引&#xff0c;与搜索引擎使用的倒排索引&#xff08;Inverted Index&#xff09;之间存在几个关键区别&#xff1a; 数据结构&#xff1a; 普通数据库索…

【Linux 10】环境变量

文章目录 &#x1f308; Ⅰ 命令行参数⭐ 1. main 函数的参数⭐ 2. main 函数参数的意义⭐ 3. 查看 argv 数组的内容⭐ 4. 命令行参数结论⭐ 5. 为什么要有命令行参数⭐ 6. 命令行参数传递由谁执行 &#x1f308; Ⅱ 环境变量基本概念⭐ 1. 常见环境变量 &#x1f308; Ⅲ 查看…

macOS Catalina for mac (macos 10.15系统)v10.15.7正式版

macOS Catalina是苹果公司专为麦金塔电脑推出的桌面操作系统&#xff0c;是macOS的第16个主要版本。它继承了苹果一贯的优雅与高效&#xff0c;不仅引入了分割视图和侧边栏&#xff0c;还带来了全新的音乐和播客应用&#xff0c;极大地提升了用户体验。在隐私保护和安全性方面&…

【Laravel】07 快速套用一个网站模板

【Laravel】07 快速套用一个网站模板 1. 新增post表2.补充 &#xff1a;生成Model、Controller、迁移文件3. 使用php artisan tinker4. 网站模板下载 课程地址 1. 新增post表 在Model中创建Post (base) ➜ example-app php artisan make:model Post Model created successfu…

ubuntu如何升级Cmake

在编译最新版的OBS时提示我cmake版本太老了&#xff1a; CMake Error at CMakeLists.txt:1 (cmake_minimum_required):CMake 3.22 or higher is required. You are running version 3.20.2先看下没升级前的版本&#xff1a; 升级一下cmake 1、下载需要的版本 https://cmake…

练习3-2 计算符号函数的值

对于任一整数n&#xff0c;符号函数sign(n)的定义如下&#xff1a; 请编写程序计算该函数对任一输入整数的值。 输入格式: 输入在一行中给出整数n。 输出格式: 在一行中按照格式“sign(n) 函数值”输出该整数n对应的函数值。 输入样例1: 10 输出样例1: sign(10) 1 输入样例…

微信小程序(3.3.5) 对实时帧数据调速

前言 小程序返回的实时帧数据帧率很高&#xff0c;需要减速处理。 处理思路&#xff1a;打开监听端口&#xff0c;监听到1帧数据&#xff0c;调用回调函数处理&#xff0c;然后关闭监听端口。 把上述过程封装到一个函数里&#xff0c;再用setInterval()函数按一定时间调用1次…