【C++】异步任务结果传递:promise、future 与 packaged_task
前面介绍了condition_variable解决的是“线程没有任务时如何等待有任务时如何被唤醒”的问题。接下来还有一个很常见的问题子线程执行了一个任务以后任务的返回值怎么交给主线程例如int Add(int a, int b) { return a b; }如果直接创建线程std::thread t(Add, 10, 20);线程当然可以执行Add()但是返回值30怎么拿回来普通std::thread本身不会直接帮我们保存函数返回值。C 提供了std::promise std::future std::packaged_task来解决线程之间的结果传递问题。一、future 可以理解成“未来才能拿到的结果”先从future开始理解。future这个单词本身就是未来所以std::futureint可以理解为现在结果可能还没有计算完成但是未来某个时刻可以通过这个对象拿到一个int。例如std::futureint future;此时future并不直接保存结果它更像是一张“取结果的凭证”。真正获取结果时int result future.get();如果结果已经准备好了get()直接返回。如果结果还没有准备好future.get() ↓ 当前线程等待 ↓ 其他线程完成计算 ↓ 结果准备完成 ↓ get返回结果所以future经常用于主线程提交任务 ↓ 子线程执行任务 ↓ 主线程以后通过future拿结果不过future自己并不能生产结果它需要和std::promise或者std::packaged_task配合使用。二、promise future一个线程设置结果另一个线程获取promise可以理解成我承诺以后会提供一个结果。例如std::promiseint promise;表示未来会产生一个int类型的结果。通过std::futureint future promise.get_future();可以拿到与这个promise对应的future。二者之间的关系可以理解成promise 负责写入结果 ↓ 共享状态 ↑ future 负责读取结果完整示例#include future #include iostream #include thread void Calculate(std::promiseint promise) { int result 10 20; // 将结果设置到共享状态中 promise.set_value(result); } int main() { std::promiseint promise; std::futureint future promise.get_future(); std::thread worker(Calculate, std::move(promise)); // 等待子线程设置结果 int result future.get(); std::cout result result \n; worker.join(); return 0; }输出result 30整个流程是主线程创建promise ↓ 通过get_future得到future ↓ promise交给子线程 ↓ 子线程计算 ↓ promise.set_value(30) ↓ future收到结果 ↓ future.get()返回30这里有一个容易注意到的地方std::thread worker(Calculate, std::move(promise));为什么需要std::move(promise)因为std::promise不能被普通复制。也就是说下面通常不行std::promiseint p1; std::promiseint p2 p1;它只能移动std::promiseint p2 std::move(p1);因为一个promise对应一个共享状态如果随便复制多个promise到底谁负责设置结果就会变得混乱。set_value 是干什么的子线程执行promise.set_value(result);表示结果已经准备完成可以通知 future 了。如果主线程此时正执行future.get();那么它会被唤醒并取得结果。future.get() 只能调用一次例如int result1 future.get(); int result2 future.get();第二次通常会出现错误。因为get()会把共享状态中的结果取走。所以future.get()更像是“领取结果”而不是反复读取变量。如果需要多个地方共享同一个异步结果可以使用std::shared_future不过普通线程池任务返回值大多数情况下std::future已经够用了。三、异常也可以通过 promise 传递异步任务不一定成功。例如int Divide(int a, int b) { if (b 0) { throw std::runtime_error(除数不能为0); } return a / b; }子线程如果直接抛出异常主线程不方便直接得到这个异常。promise可以把异常也保存到共享状态中void Calculate(std::promiseint promise) { try { int result 10 / 0; promise.set_value(result); } catch (...) { promise.set_exception(std::current_exception()); } }主线程try { int result future.get(); } catch (const std::exception e) { std::cout e.what() \n; }完整逻辑子线程正常完成 ↓ promise.set_value() ↓ future.get()得到结果 子线程发生异常 ↓ promise.set_exception() ↓ future.get()重新抛出异常也就是说future不仅可以传递正常返回值也可以把子线程中的异常传递回调用线程。这对于线程池非常重要因为任务执行失败后主线程也需要知道失败原因。四、packaged_task自动把函数返回值放进 future如果每次都手动写std::promiseint promise; std::futureint future promise.get_future();然后在线程函数中promise.set_value(result);会显得比较麻烦。于是 C 提供了std::packaged_task它可以直接包装一个可调用对象并自动把返回值保存到future中。例如int Add(int a, int b) { return a b; }使用packaged_taskstd::packaged_taskint(int, int) task(Add);这里int(int, int)表示这个任务接收两个int参数 返回一个int然后获取结果std::futureint future task.get_future();最后把任务交给线程std::thread worker(std::move(task), 10, 20);完整代码#include future #include iostream #include thread int Add(int a, int b) { return a b; } int main() { std::packaged_taskint(int, int) task(Add); std::futureint future task.get_future(); std::thread worker(std::move(task), 10, 20); int result future.get(); std::cout result result \n; worker.join(); return 0; }输出result 30这里不需要自己写promise.set_value();packaged_task会自动完成执行Add(10, 20) ↓ 得到30 ↓ 把30保存到共享状态 ↓ future.get()得到30所以可以把packaged_task理解成一个能够自动保存函数执行结果的任务包装器。它非常适合线程池。线程池提交任务时通常会把任意函数包装成std::packaged_taskreturn_type()然后把future返回给调用者。例如auto task std::make_sharedstd::packaged_taskint()([]() { return 10 20; }); std::futureint future task-get_future();真正执行任务时(*task)();调用者以后int result future.get();就可以拿到任务结果。这其实就是很多线程池submit()函数的核心思路。五、promise、future、packaged_task 应该怎么理解三者关系可以简单总结为类型主要作用std::promise手动设置结果或异常std::future获取未来产生的结果std::packaged_task包装函数并自动保存返回值promise future更适合手动控制结果产生过程std::promiseint promise; std::futureint future promise.get_future();生产者线程promise.set_value(100);消费者线程int value future.get();而packaged_task future更适合“执行一个函数然后拿到返回值”std::packaged_taskint() task([]() { return 100; }); std::futureint future task.get_future(); task(); int result future.get();在线程池中通常更常见的是用户提交函数 ↓ packaged_task包装函数 ↓ future返回给用户 ↓ task放入任务队列 ↓ 工作线程取出task ↓ 执行task ↓ 返回值自动进入future ↓ 用户future.get()取得结果这也是为什么线程池代码中经常能看到std::future和std::packaged_task一起出现。这一篇最需要掌握的是future负责拿结果 promise负责手动设置结果 promise和future通过共享状态传递数据 future.get()在结果没准备好时会等待 promise可以通过set_exception传递异常 packaged_task可以自动保存函数返回值 线程池通常使用packaged_task包装任务 再把future返回给任务提交者。0voice · GitHub

相关新闻

智能问数POC验证方法论:八个必测场景,告别“demo好看上线翻车“

智能问数POC验证方法论:八个必测场景,告别“demo好看上线翻车“

摘要:传统智能问数/ChatBI的POC做法——准备50个"XX指标是多少"类问题统计准确率——已完全失效。本文提出面向新一代智能问数的POC验证方法论,包括八个必测场景(复杂归因、多轮追问、完整闭环、追问解释、纠错重测、多类型分析、数…

2026/11/1 23:11:27 阅读更多 →
线缆盘绕的电感效应:从原理到实践的风险评估与解决方案

线缆盘绕的电感效应:从原理到实践的风险评估与解决方案

这次我们来看一个在UPS电源、数据中心和工业布线中经常被讨论的实用问题:电源线或信号线盘绕成螺旋状,是否会产生电感效应,以及对设备运行是否存在潜在影响?这个问题看似简单,却直接关系到电气系统的稳定性、设备寿命和…

2026/11/1 21:26:44 阅读更多 →
维护开源项目时,怎样处理卡顿与后台任务泄露

维护开源项目时,怎样处理卡顿与后台任务泄露

维护开源项目时,怎样处理卡顿与后台任务泄露 开源项目的卡顿反馈通常来自不同环境:有人在 CI 里挂住,有人在桌面端看到命令不退出。把这些报告直接归因于“协程泄露”并不可靠。维护者需要的是能让贡献者补齐证据的排查路径,而不是…

2026/10/31 14:35:34 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/1 0:24:43 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/1 10:36:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/1 10:36:51 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/1 1:06:58 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/1 10:35:11 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/1 9:35:41 阅读更多 →

日新闻

周新闻

月新闻