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,一经查实,立即删除!

相关文章

monorepo mode vs standard mode使用场景和各自的优缺点

NestJS 是一个基于 TypeScript 和 Node.js 的渐进式框架&#xff0c;用于构建高效、可扩展的服务器端应用程序。在 NestJS 中&#xff0c;“monorepo”和“standard mode”分别指代两种不同的项目组织和管理方式。以下是这两种模式的优缺点对比&#xff1a; Monorepo (Monolit…

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

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

基于token进行登录,每次请求携带token

一&#xff0c;什么是token&#xff1f; Token&#xff0c;也称为“令牌”&#xff0c;是服务端生成的一串字符串&#xff0c;以作客户端进行请求的一个令牌&#xff0c;当第一次登录后&#xff0c;服务器生成一个Token便将此Token返回给客户端&#xff0c;以后客户端只需带上…

蓝桥杯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 下要设置很多权限, 但是也不影响其丝滑的控制. 虽然用着舒服, 但是向日葵…

tcp通信协议

#include <myhead.h> #define IP "192.168.124.73" #define PORT 8888 int main(int argc, const char *argv[]) { //创建流式套接字 int sfd socket(AF_INET,SOCK_STREAM,0); if(sfd < 0){ fprintf(stderr,"line%d",__LI…

mysql的约束和表关系

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

实体店引流客户的最快方法是什么?线上短视频+直播引流!

前言&#xff1a;为什么想到写这个话题&#xff1f;因为在每周三晚在视频号白杨SEO免费直播的问题解答的时候&#xff0c;有个朋友问同城流量怎么做&#xff1f;我就以实体店举例解答说了做推荐搜索流量相结合。我想应该还有一些朋友也想知道&#xff0c;所以就分享出来&#x…

组合总和(Lc39)——排序+剪枝+回溯

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如…

python的练习

python 练习 一、练习题目二、代码概览 一、练习题目 Hello World 实例数字求和平方根二次方程计算三角形的面积计算圆的面积随机数生成摄氏温度转华氏温度交换变量if 语句判断字符串是否为数字判断奇数偶数判断闰年获取最大值函数质数判断输出指定范围内的素数阶乘实例九九乘…

MyBatis多参数查询解析

参考官网 1. 底层 底层是ParamNameResolver类查看getNameParams方法实现 2. 获取参数的两种方式 MyBatis获取参数值的两种方式&#xff1a; ${} : 本质就是字符串拼接#{} :本质就是占位符赋值 3. 多种情况的获取情况 单参数情况&#xff1a; a. 单参数-单个字面量类型 …

人脸清晰修复神器CodeFormer

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

Java知识总结-基础

Java中的访问权限修饰符 Java语言有四个权限访问修饰符&#xff0c;权限从大到小依次为&#xff1a; 1&#xff09;public &#xff1a;公共权限&#xff0c;可以被任意类访问&#xff0c;不同包不同类依然可以访问&#xff0c; 可修饰&#xff1a;类、成员变量、方法&#…

大模型+多模态实现

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

STM32中SPI通信的完整C语言代码范例

SPI (Serial Peripheral Interface) 是一种同步串行通信协议&#xff0c;广泛应用于嵌入式系统中&#xff0c;用于与外设进行数据交换。STM32系列微控制器提供了丰富的SPI外设&#xff0c;支持多种工作模式和配置选项。本文将以STM32F103系列为例&#xff0c;详细介绍SPI通信的…

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…

一些RabbitMQ面试题

RabbitMQ是什么&#xff0c;它的主要用途是什么 RabbitMQ是一个开源的消息代理软件&#xff0c; 它实现了高级消息队列协议&#xff08;AMQP&#xff09;&#xff0c; 主要用于在分布式系统之间进行可靠的异步通信。 它的主要用途包括解耦系统组件、提高系统可扩展性、实现消息…

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

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

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

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