C++多线程系列——std::future | std::promise

获得线程执行任务的结果

在 C++ 11 之前,想要从线程返回执行任务的结果,可以通过指针来完成。

void fun(int x, int y, int* ans, std::condition_variable &cv) {// 模拟求值之前的准备工作this_thread::sleep_for(3s);*ans = x + y;cv.notify_one();
}int main()
{int a = 10;int b = 8;int sum = 0;std::mutex m;std::condition_variable cv;std::thread t(fun, a, b, &sum, std::ref(cv));// 模拟主线程中的其他操作this_thread::sleep_for(1s);unique_lock<mutex> l{m};cv.wait(l);std::cout << sum << std::endl; // 输出:18t.join();return 0;
}

可以看到,要通过指针来传递结果,在操作上比较复杂需要涉及到 mutexunique_lockcondition_variable ,且逻辑上没有那么舒服。因此 C++ 提供了 std::future 类模板。

std::future

C++11 提供了 std::future 类模板,future 对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。
事实上,一个 std::future 对象在内部存储一个将来会被某个 provider 赋值的值,并提供了一个访问该值的机制,通过get()成员函数实现。但如果有人试图在值可用之前通过get()来访问相关的值,那么get()函数将会阻塞,直到该值可用。

逻辑上来说 std::future 是“一次性事件”,也就是说这个事件只会发生一次,发生之后就会被销毁无法再次使用。既然是一个事件,在时间跨度上来说,必然存在两种状态:即事件发生前和事件发生后。对应的 std::future 也存在两种状态,即未就绪状态和就绪状态,当std::future处于非就绪状态时,就表示执行的任务还没有得出结果,此时调用get(),调用线程会等待任务线程执行完成;当std::future处于就绪状态时,就表示执行的任务已经得出结果,可以通过 get() 立即取得值。既然前文提到了 std::future 表示的是一次性事件,“一次性”是什么意思?“一次性”表示只能调用 get() 方法一次,以后就不能再调用 get() 了。要知道 std::future 是否调用过 get() ,可以通过成员函数 vaild() 。

一个有效的std::future对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,他们分别是:

  • std::async
  • std::promise::get_future
  • std::packaged_task::get_future

std::async

对于简单的我们不需要获得结果的任务(即没有返回值的可调用对象)来说,使用 std::thread 可以轻松地完成。然而当我们需要任务的结果,std::thread 并不能满足要求,我们需要求助于函数模板 std::asyncstd::async 在具有 std::thread 功能的基础上提供了返回值 std::future(这是一个一次性任务)。通过 std::future 可以获得任务的结果(调用 std::future)。下面给一个小 demo:

int sayHello(){cout << "hello world" << endl;return 1;
}int main() {std::future<int> future = std::async(sayHello);cout << future.get() << endl;// cout << future.get() << endl; // 上文提到了一次性任务,简单理解就是只能调用一次 get() 方法获得任务的结果,// 当第二次调用 get() 时就会报错,因为这已经被使用过一次。return 0;
}

使用 std::async 并通过 std::future 获得任务结果并不是唯一将任务与结果关联的方式,使用 std::packaged_task 可以更好得完成这项操作,同时其也可作为线程池的基本构件。 本质上来说 std::packaged_task 连结了 std::future 对象与可调用对象,将模板函数 std::async 进一步抽象成了一个模板类从而方便使用。

std::packaged_task

下面是 std::packaged_task 的部分类定义,可以了解到其模板参数是函数签名。

template<typename _Res, typename... _ArgTypes>class packaged_task<_Res(_ArgTypes...)>

前文提到 std::packaged_task 连结了可调用对象,其本身也是个可调用对象,我们可以直接调用,还可以将其包装在 std::function 对象内,当做入参传递给另一个线程 std::thread 来调用,也可以给任何需要可调用对象的函数。std::packaged_task 对象作为函数对象在被调用的过程中, 会通过函数调用操作符接受参数,并将其进一步传递给包装的任务函数,由其运行得出结果,并将结果保存到关联的 std::future 对象内部。对于外部来说可以通过调用 std::packaged_task 的成员函数 get_future() 来获得关联的 std::future 。下面给出小的使用demo:

int sayHello(){cout << "hello world" << endl;return 1;
}
int main() {std::packaged_task<int()> task{sayHello};std::thread t1{[&](){task();}};std::future<int> future = task.get_future();t1.join();cout << future.get() << endl;return 0;
}

这里需要注意的是 std::packaged_task 删除了拷贝构造和拷贝赋值函数(因此在传递的时候需要用到 std::move)。
在这里插入图片描述

std::promise

std::packaged_task 关联了一个 std::future 一样,std::promise 也关联了一个 std::future,可以通过其成员函数 get_future() 获得。相比于通过 std::packaged_task 必须关联一个可调用对象才能用,std::promise 的使用自由度更高。可以将一对 std::promisestd::future 对象分别传给不同线程,来让两个线程传值。赋值线程通过调用 std::promise 的成员函数 set_value() 方法可以设置值, 需要值的线程通过调用 std::futureget() 来取得值。

void fun(int x, int y, std::promise<int>& promiseObj) {promiseObj.set_value(x + y);
}int main()
{int a = 10;int b = 8;std::promise<int> promiseObj;std::future<int> futureObj = promiseObj.get_future();std::thread t(fun, a, b, std::ref(promiseObj));t.join();int sum = futureObj.get();std::cout << "sum=" << sum << std::endl; // 输出:18return 0;
}

在任务线程中抛出异常该如何被调用线程获取

在生产环境中的程序往往会遇到各种异常,比如,硬盘写满、网络故障、数据库崩溃等。但是当任务线程抛出异常时,调用线程该如何知道任务线程抛出了异常而不是任务结果。想想这是一个非常难处理的问题,幸运的是 std::future 会保存抛出的异常。经由 std::sync() 调用的函数或者包装了任务函数的 std::packaged_task 在执行任务抛出异常值时,异常会代替本该被设定的值被保存在 future 中。 当在调用线程中调用 std::future 成员函数 get() 时,异常被重新在调用线程中抛出抛出。下面是一个小demo:

double square_root(double x){if(x < 0){throw std::out_of_range(" x < 0"); // 异常被保存在 future 上}return sqrt(x);
}int main() {std::future<double> f = std::async(square_root, -1); // 在子线程中运行 square_root 任务double y = f.get(); // 在调用 get() 方法时重新抛出异常return 0;
}

参考

【C++11 多线程】future与promise(八)
C++ 并发编程实战(第二版)

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

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

相关文章

一文掌握Vue3:深度解读Vue3新特性、Vue2与Vue3核心差异以及Vue2到Vue3转型迭代迁移重点梳理与实战

每次技术革新均推动着应用性能与开发体验的提升。Vue3 的迭代进步体现在性能优化、API重构与增强型TypeScript支持等方面&#xff0c;从而实现更高效开发、更优运行表现&#xff0c;促使升级成为保持竞争力与跟进现代前端趋势的必然选择。本文深度解读Vue3 响应式数据data、生命…

蓝桥杯python考级整理

4_1:算术运算符 4_2:基本语法 4_3:基本语法 4_4:列表 4_5:函数 4_6:字符串 4_7:列表 4_8:逻辑运算符 4_9:字典 4_10:函数

MacOS通过命令行开启关闭向日葵远程控制的后台服务

categories: [Tips] tags: MacOS Tips 写在前面 经常有小伙伴问我电脑相关的问题, 而解决问题的一个重要途径就是远程了. 关于免费的远程工具我试过向日葵和 todesk, 并且主要使用向日葵, 虽然 MacOS 下要设置很多权限, 但是也不影响其丝滑的控制. 虽然用着舒服, 但是向日葵…

mysql的约束和表关系

根据查询的结果&#xff0c;复制出一个新表 create table newTable AS select * from oldTable; create table newPeople AS select * from day2_test.people; 约束 引入&#xff1a;如果某一列如id列&#xff0c;有重复的数据&#xff0c;无法准确定位&#xff0c;有的列有空…

人脸清晰修复神器CodeFormer

随着AI技术在图像处理领域大展身手&#xff0c;AI去马赛克相关的项目也屡见不鲜&#xff0c;比如在Github上开源免费、备受欢迎的 CodeFormer 。不得不说利用这款神奇的人脸修复工具&#xff0c;真的是让我大开眼界&#xff0c;竟然可以这样搞&#xff01; 不管面对的是多么模…

大模型+多模态实现

那么如何在预训练LLM的基础上引入跨模态的信息&#xff08;包括图像、语音、视频模态&#xff09;&#xff0c;让其变得更强大、更通用呢&#xff1f;本节将介绍“大模型多模态”的3种实现方法。 以LLM为核心&#xff0c;调用其他多模态组件 微软亚洲研究院&#xff08;MSRA&…

Java基础(运算符)

运算符 运算符和表达式 运算符&#xff1a;对字面量或者变量进行操作的符号 表达式&#xff1a;用运算符把字面量或者变量连接起来&#xff0c;符合java语法的式子就可以称为表达式&#xff1b;不同运算符连接的表达式体现的是不同类型的表达式。 算术运算符&#xff08;加…

Linux基础命令[24]-su

文章目录 1. su 命令说明2. su 命令语法3. su 命令示例3.1 不加参数3.2 -&#xff08;登录&#xff09;3.3 -c&#xff08;执行命令&#xff09; 4. 总结 1. su 命令说明 su&#xff1a;以用户身份执行命令&#xff0c;基本信息如下&#xff1a; Usage:su [options] [-] [USE…

数据结构四:线性表之带头结点的单向循环循环链表的设计

前面两篇介绍了线性表的顺序和链式存储结构&#xff0c;其中链式存储结构为单向链表&#xff08;即一个方向的有限长度、不循环的链表&#xff09;&#xff0c;对于单链表&#xff0c;由于每个节点只存储了向后的结点的地址&#xff0c;到了尾巴结点就停止了向后链的操作。也就…

架构师系列-消息中间件(九)- RocketMQ 进阶(三)-消费端消息保障

5.2 消费端保障 5.2.1 注意幂等性 应用程序在使用RocketMQ进行消息消费时必须支持幂等消费&#xff0c;即同一个消息被消费多次和消费一次的结果一样&#xff0c;这一点在使用RoketMQ或者分析RocketMQ源代码之前再怎么强调也不为过。 “至少一次送达”的消息交付策略&#xff…

Hive主要介绍

Hive介绍 hive是基于 Hadoop平台操作 HDFS 文件的插件工具 可以将结构化的数据文件映射为一张数据库表 可以将 HQL 语句转换为 MapReduce 程序 1.hive 是由驱动器组成&#xff0c;驱动器主要由4个组件组成&#xff08;解析器、编译器、优化器、执行器&#xff09; 2.hive本身不…

【安卓13-Framework】SystemUI定制之屏蔽下拉状态栏部分快捷按钮

1、需求 屏蔽下拉状态栏谷歌录屏、省电模式、二维码扫描器等快捷按钮。 2、修改路径 普及&#xff1a;安卓的SystemUI包提供了状态栏、导航栏、通知中心等重要的用户界面元素。 状态栏小部件UI显示修改路径&#xff1a;frameworks/base/packages/SystemUI/src/com/android/s…

Java虚拟机(jvm)常见问题总结

1.电脑怎样认识我们编写的Java代码 首先先了解电脑是二进制的系统&#xff0c;他只认识 01010101比如我们经常要编写 HelloWord.java 电脑是怎么认识运行的HelloWord.java是我们程序员编写的&#xff0c;我们人可以认识&#xff0c;但是电脑不认识 Java文件编译的过程 1. 程…

git lab 2.7版本修改密码命令

1.gitlab-rails console -e production Ruby: ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-linux] GitLab: 14.9.0-jh (51fb4a823f6) EE GitLab Shell: 13.24.0 PostgreSQL: 12.7 2根据用户名修改密码 user User.find_by(username: ‘username’) # 替换’use…

无人驾驶(移动机器人)路径规划之RRT与RRTStar算法及其matlab实现

在自动驾驶与移动机器人路径规划时&#xff0c;必定会用到经典的算法RRT与RRT Star。下面是RRT与RRTStar的matlab实现效果。可以发现RRTStar效果明显改善。 目录 一、效果比较 1.1 RRT算法效果&#xff08;黑色为障碍物&#xff0c;红色线为最终路径&#xff0c;蓝色三角形为…

C++之STL-vector+模拟实现

目录 一、vector的介绍和基本使用的方法 1.1 介绍 1.2 迭代器 1.3 vector的一些基本使用 1.3.1 构造函数 1.3.2 迭代器 1.3.3 有关容量的接口 1.3.4 增删查改 二、模拟实现vector 2.1 成员变量 2.2 迭代器的实现 2.3 容量接口的实现 2.3.1 size函数实现 2.3.2 capa…

阿斯达年代记三强争霸新手开荒注意事项 搬砖攻略和注意问题分享

阿斯达年代记三强争霸新手开荒注意事项 搬砖攻略和注意问题分享 阿斯达年代三强争霸这款游戏刚开始公测就获得了玩家们的集体关注&#xff0c;这是一款根据影视剧改编的MMORPG游戏&#xff0c;玩家将置身于名为阿斯大陆的奇幻世界&#xff0c;加入阿斯达、亚高、不法者三大势力…

Prompt之美:如何设计提示词让大模型变“聪明”

目录 一. Prompt关键要素 二. Prompt技巧 三. 实战中的Prompt优化 四. 参考文献 一. Prompt关键要素 Prompt是一个简短的文本输入&#xff0c;用于引导AI模型生成特定的回答或执行特定任务。换句话说&#xff0c;Prompt是你与AI模型沟通的方式。一个好的Prompt可以让AI更准…

从现在开始:让AI写代码,你只负责敲tab键

如果你是一名程序员&#xff0c;你一定有过这样的经历&#xff1a;在编写代码的时候&#xff0c;突然遇到了一个棘手的问题&#xff0c;需要花费大量的时间去查找资料、尝试不同的解决方案&#xff0c;甚至有时候还需要去问同事或者在网上寻求帮助。这样的情况不仅会浪费你的时…

用立方样条联合SHAP分析在危险因素鉴定中的作用

用立方样条联合SHAP分析在危险因素鉴定中的作用 1. SHAP分析告诉我们变量之间的关系 SHAP分析计算的SHAP值代表了某变量对于结局指标的贡献&#xff0c;代表了相关性的趋势&#xff0c;SHAP分析中的散点图是对以上关系的可视化&#xff0c;从中我们可以直观看到随着变量值的变…