C++11特性:多线程异步操作

1. std::future

C++11中增加的线程类,使得我们能够非常方便的创建和使用线程,但有时会有些不方便,比如需要获取线程返回的结果,就不能通过join()得到结果,只能通过一些额外手段获得,比如:定义一个全局变量,在子线程中赋值,在主线程中读这个变量的值,整个过程比较繁琐。C++提供的线程库中提供了一些类用于访问异步操作的结果

那么,什么叫做异步呢?

我们去星巴克买咖啡,因为都是现磨的,所以需要等待,但是我们付完账后不会站在柜台前死等,而是去找个座位坐下来玩玩手机打发一下时间,当店员把咖啡磨好之后,就会通知我们过去取,这就叫做异步。

顾客(主线程)发起一个任务(子线程磨咖啡),磨咖啡的过程中顾客去做别的事情了,有两条时间线(异步
顾客(主线程)发起一个任务(子线程磨咖啡),磨咖啡的过程中顾客没去做别的事情而是死等,这时就只有一条时间线(同步),此时效率相对较低。
因此多线程程序中的任务大都是异步的,主线程和子线程分别执行不同的任务,如果想要在主线中得到某个子线程任务函数返回的结果可以使用C++11提供的std:future类,这个类需要和其他类或函数搭配使用,先来介绍一下这个类的API函数:

1.1 成员函数:

1.1.1 类的定义:

通过类的定义可以得知,future是一个模板类,也就是这个类可以存储任意指定类型的数据。

// 定义于头文件 <future>
template< class T > class future;
template< class T > class future<T&>;
template<>          class future<void>;

1.1.2 构造函数: 

// ①
future() noexcept;
// ②
future( future&& other ) noexcept;
// ③
future( const future& other ) = delete;

构造函数①:默认无参构造函数。
构造函数②:移动构造函数,转移资源的所有权。
构造函数③:使用=delete显示删除拷贝构造函数, 不允许进行对象之间的拷贝 。

1.1.3 常用成员函数(public): 

一般情况下使用=进行赋值操作就进行对象的拷贝,但是future对象不可用复制,因此会根据实际情况进行处理:

1. 如果other右值,那么转移资源的所有权
2. 如果other非右值不允许进行对象之间的拷贝(该函数被显示删除禁止使用)。

future& operator=( future&& other ) noexcept;
future& operator=( const future& other ) = delete;

取出future对象内部保存的数据,其中void get()是为future<void>准备的,此时对象内部类型就是void,该函数是一个阻塞函数,当子线程的数据就绪后解除阻塞就能得到传出的数值了。

T get();
T& get();
void get();

因为future对象内部存储的是异步线程任务执行完毕后的结果,是在调用之后的将来得到的,因此可以通过调用wait()方法,阻塞当前线程,等待这个子线程的任务执行完毕,任务执行完毕当前线程的阻塞也就解除了。

void wait() const;

如果当前线程wait()方法只会死等,直到子线程任务执行完毕将返回值写入到future对象中,调用wait_for()只会让线程阻塞一定的时长,但是这样并不能保证对应的那个子线程中的任务已经执行完毕了。

wait_until()wait_for()函数功能是差不多,前者是阻塞到某一指定的时间点,后者是阻塞一定的时长。

template< class Rep, class Period >
std::future_status wait_for( const std::chrono::duration<Rep,Period>& timeout_duration ) const;template< class Clock, class Duration >
std::future_status wait_until( const std::chrono::time_point<Clock,Duration>& timeout_time ) const;

wait_until()wait_for()函数返回之后,并不能确定子线程当前的状态,因此我们需要判断函数的返回值,这样就能知道子线程当前的状态了:

1. future_status::deferred    子线程中的任务函仍未启动
2. future_status::ready    子线程中的任务已经执行完毕,结果已就绪
3. future_status::timeout    子线程中的任务正在执行中,指定等待时长已用完

 2. std::promise

 std::promise是一个协助线程赋值的类,它能够将数据和future对象绑定起来,为获取线程函数中的某个值提供便利。

2.1 类成员函数:

 2.1.1 类定义:

 通过std::promise类的定义可以得知,这也是一个模板类,我们要在线程中传递什么类型的数据,模板参数就指定为什么类型。

// 定义于头文件 <future>
template< class R > class promise;
template< class R > class promise<R&>;
template<>          class promise<void>;

2.1.2 构造函数: 

// ①
promise();
// ②
promise( promise&& other ) noexcept;
// ③
promise( const promise& other ) = delete;

构造函数①:默认构造函数,得到一个空对象。
构造函数②:移动构造函数。
构造函数③:使用=delete显示删除拷贝构造函数, 不允许进行对象之间的拷贝 。

2.1.3 公共成员函数:

std::promise类内部管理着一个future类对象,调用get_future()就可以得到这个future对象了。

std::future<T> get_future();

存储要传出的 value 值,并立即让状态就绪,这样数据被传出其它线程就可以得到这个数据了。重载的第四个函数是为promise<void>类型的对象准备的。

void set_value( const R& value );
void set_value( R&& value );
void set_value( R& value );
void set_value();

存储要传出的 value 值,但是不立即令状态就绪在当前线程退出时,子线程资源被销毁,再令状态就绪。 

void set_value_at_thread_exit( const R& value );
void set_value_at_thread_exit( R&& value );
void set_value_at_thread_exit( R& value );
void set_value_at_thread_exit();

 2.2 promise的使用:

通过promise传递数据的过程一共分为5步:

1. 在主线程中创建std::promise对象。
2. 将这个std::promise对象通过引用的方式传递给子线程的任务函数。
3. 在子线程任务函数中给std::promise对象赋值。
4. 在主线程中通过std::promise对象取出绑定的future实例对象。
5. 通过得到的future对象取出子线程任务函数中返回的值。

子线程任务函数执行期间,让状态就绪:

#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{promise<int> pr;thread t1([](promise<int> &p) {p.set_value(100);this_thread::sleep_for(chrono::seconds(3));cout << "睡醒了...." << endl;}, ref(pr));future<int> f = pr.get_future();int value = f.get();cout << "value: " << value << endl;t1.join();return 0;
}

示例程序输出的结果:

value: 100
睡醒了....

示例程序的中子线程的任务函数指定的是一个匿名函数,在这个匿名的任务函数执行期间通过p.set_value(100);传出了数据并且激活了状态,数据就绪后,外部主线程中的int value = f.get();解除阻塞,并将得到的数据打印出来,5秒钟之后子线程休眠结束,匿名的任务函数执行完毕。

 子线程任务函数执行结束,让状态就绪:

#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{promise<int> pr;thread t1([](promise<int> &p) {p.set_value_at_thread_exit(100);this_thread::sleep_for(chrono::seconds(3));cout << "睡醒了...." << endl;}, ref(pr));future<int> f = pr.get_future();int value = f.get();cout << "value: " << value << endl;t1.join();return 0;
}

示例程序输出的结果:

睡醒了....
value: 100

在示例程序中,子线程的这个匿名的任务函数中通过p.set_value_at_thread_exit(100);执行完毕并退出之后才会传出数据并激活状态,数据就绪后,外部主线程中的int value = f.get();解除阻塞,并将得到的数据打印出来,因此子线程在休眠5秒钟之后主线程中才能得到传出的数据

另外,在这两个实例程序中有一个知识点需要强调,在外部主线程中创建的promise对象必须要通过引用的方式传递到子线程的任务函数中,在实例化子线程对象的时候,如果任务函数的参数是引用类型,那么实参一定要放到std::ref()函数中,表示要传递这个实参的引用到任务函数中。

3. std::packaged_task         

 std::packaged_task类包装了一个可调用对象包装器类对象(可调用对象包装器包装的是可调用对象,可调用对象都可以作为函数来使用)。

 3.1 类成员函数:

3.1.1 类的定义:

通过类的定义可以看到这也是一个模板类,模板类型和要在线程中传出的数据类型是一致的。

// 定义于头文件 <future>
template< class > class packaged_task;
template< class R, class ...Args >
class packaged_task<R(Args...)>;

 3.1.2 构造函数:

// ①
packaged_task() noexcept;
// ②
template <class F>
explicit packaged_task( F&& f );
// ③
packaged_task( const packaged_task& ) = delete;
// ④
packaged_task( packaged_task&& rhs ) noexcept;

构造函数①:无参构造,构造一个无任务的空对象。
构造函数②:通过一个可调用对象,构造一个任务对象。
构造函数③:显示删除,不允许通过拷贝构造函数进行对象的拷贝
构造函数④:移动构造函数。 

3.1.3 常用公共成员函数:

 通过调用任务对象内部的get_future()方法就可以得到一个future对象,基于这个对象就可以得到传出的数据了。

std::future<R> get_future();

3.2 packaged_task的使用: 

packaged_task其实就是对子线程要执行的任务函数进行了包装,和可调用对象包装器的使用方法相同,包装完毕之后直接将包装得到的任务对象传递给线程对象就可以了。

#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{packaged_task<int(int)> task([](int x) {return x += 100;});thread t1(ref(task), 100);future<int> f = task.get_future();int value = f.get();cout << "value: " << value << endl;t1.join();return 0;
}

在上面的示例代码中,通过packaged_task类包装了一个匿名函数作为子线程的任务函数,最终的得到的这个任务对象需要通过引用的方式传递到子线程内部,这样才能在主线程的最后通过任务对象得到future对象,再通过这个future对象取出子线程通过返回值传递出的数据。

 4. std::async

std::async函数比前面提到的std::promisepackaged_task更高级一些,因为通过这函数可以直接启动一个子线程并在这个子线程中执行对应的任务函数,异步任务执行完成返回的结果也是存储到一个future对象中,当需要获取异步任务的结果时,只需要调用future类的get()方法即可,如果不关注异步任务的结果,只是简单地等待任务完成的话,可以调用future类的wait()wait_for()方法。该函数的函数原型如下:

// 定义于头文件 <future>
// ①
template< class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>async( Function&& f, Args&&... args );// ②
template< class Function, class... Args >
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>async( std::launch policy, Function&& f, Args&&... args );

可以看到这是一个模板函数,在C++11中这个函数有两种调用方式:

函数①:直接调用传递到函数体内部的可调用对象,返回一个future对象。
函数②:通过指定的策略调用传递到函数内部的可调用对象,返回一个future对象。

函数参数:

1. f:可调用对象,这个对象在子线程中被作为任务函数使用。

2. Args:传递给 f 的参数(实参)。

3. policy:可调用对象 的执行策略。

 策略:

1. std::launch::async    调用async函数时创建新的线程执行任务函数

2. std::launch::deferred    调用async函数时不执行任务函数直到调用了futureget()或者wait()时才执行任务(这种方式不会创建新的线程)。

关于std::async()函数的使用,对应的示例代码如下:

4.1 方式1:

调用async()函数直接创建线程执行任务: 

#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{cout << "主线程ID: " << this_thread::get_id() << endl;// 调用函数直接创建线程执行任务future<int> f = async([](int x) {cout << "子线程ID: " << this_thread::get_id() << endl;this_thread::sleep_for(chrono::seconds(5));return x += 100;}, 100);future_status status;do {status = f.wait_for(chrono::seconds(1));if (status == future_status::deferred){cout << "线程还没有执行..." << endl;f.wait();}else if (status == future_status::ready){cout << "子线程返回值: " << f.get() << endl;}else if (status == future_status::timeout){cout << "任务还未执行完毕, 继续等待..." << endl;}} while (status != future_status::ready);return 0;
}

示例程序输出的结果为:

主线程ID: 8904
子线程ID: 25036
任务还未执行完毕, 继续等待...
任务还未执行完毕, 继续等待...
任务还未执行完毕, 继续等待...
任务还未执行完毕, 继续等待...
任务还未执行完毕, 继续等待...
子线程返回值: 200

将匿名函数传给async后就开始运行了(在后台运行,也就是另一个线程)。 匿名函数的返回值会存储f 中。

调用async()函数时不指定策略就是直接创建线程并执行任务,示例代码的主线程中做了如下操作status = f.wait_for(chrono::seconds(1));其实直接调用f.get()就能得到子线程的返回值。这里为了给大家演示wait_for()的使用,所以写的复杂了些。

 4.2 方式2:

调用async()函数不创建线程执行任务:

#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{cout << "主线程ID: " << this_thread::get_id() << endl;// 调用函数直接创建线程执行任务future<int> f = async(launch::deferred, [](int x) {cout << "子线程ID: " << this_thread::get_id() << endl;return x += 100;}, 100);this_thread::sleep_for(chrono::seconds(5));cout << f.get();return 0;
}

示例程序输出的结果:

主线程ID: 24760
主线程开始休眠5秒...
子线程ID: 24760
200

由于指定了launch::deferred 策略,因此调用async()函数并不会创建新的线程执行任务当使用future类对象调用了get()或者wait()方法后才开始执行任务(此处一定要注意调用wait_for()函数是不行的)。

通过测试程序输出的结果可以看到,两次输出的线程ID是相同的任务函数是在主线程中被延迟(主线程休眠了5秒)调用了。因为没有创建的新的线程,线程需要自己创建

最终总结:

1. 使用async()函数,是多线程操作中最简单的一种方式,不需要自己创建线程对象,并且可以得到子线程函数的返回值。
2. 使用std::promise类,在子线程中可以传出返回值也可以传出其他数据,并且可选择在什么时机将数据从子线程中传递出来,使用起来更灵活。
3. 使用std::packaged_task类,可以将子线程的任务函数进行包装,并且可以得到子线程的返回值。

本文参考:多线程异步操作 | 爱编程的大丙 (subingwen.cn)

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

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

相关文章

无人职守自动安装linux操作系统

无人职守自动安装linux操作系统 1. 大规模部署案例2. PXE 技术3. Kickstart 技术4. 配置安装服务器4.1 DHCP服务4.2 TFTP 服务4.3 NFS服务 5. 示例5.1 搭建server1. 启动dhcp并设为开机自启2. 设置并启动tftp3. 将客户端所需启动文件复制到TFTP服务器4. 创建Kickstart自动应答文…

【IO】IO模型与零拷贝

前言&#xff1a; 正在运行的程序其实就是系统中的一个进程&#xff0c;操作系统会为每一个进程分配内存空间&#xff0c;而内存空间分为两部分&#xff0c;一部分是用户空间&#xff0c;这是用户进程访问的内存区域&#xff1b;另一部分是内核空间&#xff0c;是操作系统内核访…

20种常用的软件测试方法,建议先收藏再观看

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

简单的喷淋实验(2):(1)根据土壤湿度自动控制喷淋开关;(2)根据光照强度控制风扇以及灯的开关---嵌入式实训

目录 简单的喷淋实验(2)&#xff1a; &#xff08;1&#xff09;根据土壤湿度自动控制喷淋开关&#xff1b; &#xff08;2&#xff09;根据光照强度控制风扇以及灯的开关---嵌入式实训 任务2&#xff1a; 具体过程&#xff1a; 所用的头文件&#xff1a; data_global.h …

gin框架使用系列之三——获取表单数据

系列目录 《gin框架使用系列之一——快速启动和url分组》《gin框架使用系列之二——uri占位符和占位符变量的获取》 一、获取get参数 get请求的参数是直接加在url后面的&#xff0c;在gin中获取get请求的参数主要用Query()和DefaultQuery()两个方法&#xff0c;示例代码如下…

【Unity地形】使用地形工具创建场景环境-Terrain

如上图Unity的地形工具可以让我们实现创建复杂、丰富的3D室外环境。 我们创建地形很简单&#xff0c;在层级面板中右键-3Dobject-Terrain 就可以创建一个默认的地形模型&#xff01;这个模型是Unity内置的。 接下来的地形编辑功能全部集中在这个地形的组件上 主要功能如下&…

WPS中如何根据身份证号生成出生日期并排序

1. wps中如何根据身份证号导出出生日期并排序 1.1 wps中建一张表 1.2 使用转日期格式导出出生日期 DATE(VALUE(MID(C2,7,4)),VALUE(MID(C2,11,2)),VALUE(MID(C2,13,2)))MID(C2, 7, 4)&#xff1a;这部分从单元格 C2 中提取文本字符串&#xff0c;从第7个字符开始提取长度为4的…

[python]python使用M-LSD直线检测算法onnx部署模型实时检测

介绍 github地址&#xff1a;https://github.com/navervision/mlsd LSD (M-LSD)一种用于资源受限环境的实时轻量线段检测器。它利用了极其高效的 LSD 架构和新颖的训练方案&#xff0c;包括 SoL 增强和几何学习方案。模型可以在GPU、CPU甚至移动设备上实时运行。算法已开源&a…

python虚拟环境及其在项目实践中的应用

文章目录 1.问题的提出1.什么是python虚拟环境2.如何创建2.1第1步-为共享同一虚拟环境的项目创建共同的父目录2.2第2步-在父目录下创建虚拟python环境2.3在父目录下创建各个项目文件夹 1.问题的提出 假设我正在开发若干python项目&#xff0c;这里假定项目名分别为Project1&am…

【elk-day01】es和kibana搭建及验证---Mac-Docker

Mac系统使用Docker下载搭建和验证eskibana Docker下载安装es安装es验证kibana安装kibana验证 Docker下载安装 Docker Desktop官网安装下载地址 说明一下为什么要安装desktop版本的docker&#xff0c;因为docker作为工具使用&#xff0c;我们需要的是开箱即用&#xff0c;没有必…

windows搭建MySQL 8.25主从配置

1.本次搭建的版本 mysql-8.0.25-win-x64 2.在解压完成后的文件内并没有对应的my.ini的配置文件这个my.ini是需要的主配置文件需要自行创建。 注&#xff1a;安装路径及数据存放路径需根据实际安装情况进行修改&#xff08;其它配置信息可结合实际情况进行修改&#xff09; 3.在…

vue+element实现动态表格:根据后台返回的属性名和字段动态生成可变表格

现有一个胡萝卜厂生产不同品种的胡萝卜&#xff0c;为了便于客户了解产品&#xff0c;现需在官网展示胡萝卜信息。现有的萝卜信息&#xff1a;编号&#xff08;id&#xff09;、名称&#xff08;name&#xff09;、保质期&#xff08;age&#xff09;、特点&#xff08;remark&…

深度学习:计算机技术的革命性突破

深度学习&#xff1a;计算机技术的革命性突破 随着科技的飞速发展&#xff0c;深度学习已经成为计算机技术领域的一股强大力量。它改变了我们与机器的交互方式&#xff0c;为人工智能领域带来了革命性的突破。本篇博客将深入探讨深度学习的原理、应用和发展趋势。 一、深度学…

【python】爬取斗鱼直播照片保存到本地目录【附源码+文末免费送书】

一、导入必要的模块&#xff1a; 这篇博客将介绍如何使用Python编写一个爬虫程序&#xff0c;从斗鱼直播网站上获取图片信息并保存到本地。我们将使用requests模块发送HTTP请求和接收响应&#xff0c;以及os模块处理文件和目录操作。 如果出现模块报错 进入控制台输入&#xff…

【基础篇】五、类的双亲委派机制

文章目录 1、双亲委派机制2、Java代码中去主动加载一个类3、“父”加载器4、Q & A5、打破双亲委派机制 1、双亲委派机制 JVM中有多个类加载器&#xff0c;某个类A&#xff0c;到底该由谁去加载 ⇒ 双亲委派机制 该机制的作用&#xff1a; 保证类加载的安全性&#xff1a;避…

网页设计——中国梦

文章目录 前言一、需求分析二、技术1.CSS2.响应式布局3.实施过程三、演示四、如何运行五、源码总结前言 本项目是基于css、html的静态网页项目。使用的工具是vscode项目名称:中国梦运行:导入vscode直接运行包含内容:所有源码、ppt、计划书(文末附有链接)。一、需求分析 中…

Grafana 配置告警

配置告警 配置告警 1. Grafana 配置文件配置 #################################### SMTP / Emailing ########################## [smtp] enabled true host smtp.qq.com:587 user 9**qq.com # If the password contains # or ; you have to wrap it with triple quotes…

智能优化算法应用:基于白鲸算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于白鲸算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于白鲸算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.白鲸算法4.实验参数设定5.算法结果6.参考文献7.MA…

Unity编辑器紫色

紫色原因是因为编辑器内跑了其他平台的shader兼容性导致的&#xff0c;需要动态的去修改shader&#xff0c;主要用到Unity的api : Shader.Find(shaderName); 具体的工具代码如下&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine…

Jekins实现自动化部署

1. Jenkins 安装启动 war包安装 下载脚本 #!/bin/bash mkdir /opt/module/jenkins cd /opt/module/jenkins wget https://get.jenkins.io/war-stable/latest/jenkins.war # 版本2.277.4启动脚本 cd /opt/module/jenkins java -jar jenkins.war --httpPort9090 #访问地址&am…