当前位置: 首页 > news >正文

C++ 异步任务详解:future, promise, async

目录

1. 核心问题:线程如何“返回”结果?

2. 核心比喻:去咖啡店点单

3. std::promise 与 std::future:手动挡组合

std::promise (承诺者 / 生产者)

std::future (未来凭证 / 消费者)

代码示例:手动管理线程与结果传递

4. std::async:自动挡,一键启动

代码示例:使用 async 简化任务

5. 区别总结:我应该用哪个?

关键决策点:


1. 核心问题:线程如何“返回”结果?

在普通的单线程编程中,我们调用一个函数,它会立即返回一个结果:

int calculate() { return 100; }
int result = calculate(); // 直接拿到结果

但在多线程世界里,情况变得复杂。当我们创建一个 std::thread 来执行一个任务时,主线程不会等待它完成,而是继续往下执行。那么,主线程该如何获取子线程的计算结果呢?

int result_from_thread;
void calculate_in_thread() { // ... 做一些耗时计算 ...result_from_thread = 100; // ? 这样不安全,有数据竞争!
}std::thread t(calculate_in_thread);
// 主线程怎么知道什么时候计算完成?
// 如何安全地拿到那个 100?
t.join();

直接使用共享变量和互斥锁可以做到,但非常繁琐且容易出错。为了优雅地解决这个问题,C++11 引入了 futurepromiseasync

2. 核心比喻:去咖啡店点单

想象一下这个场景,能帮你理解这三个工具的关系:

  • 你 (主线程): 想要一杯咖啡(计算结果)。

  • 收银员 (Promise): 承诺稍后会给你做好咖啡。他给了你一个取餐小票

  • 取餐小票 (Future): 这是你未来领取咖啡的凭证。你可以拿着它去做别的事,比如看手机。

  • 咖啡师 (子线程): 在后台默默地制作咖啡。

  • get() 操作: 你拿着小票去取餐口,如果咖啡好了,你立刻拿到;如果没好,你就在那里等着 (阻塞),直到咖啡师做好递给你。

  • async (全自动点餐机): 你只需要在机器上点单,它自动下单给后台,并直接吐给你一张取餐小票 (Future)。它把收银员和咖啡师的工作细节都封装起来了。

3. std::promisestd::future:手动挡组合

promisefuture 是一对底层的、需要手动配合使用的工具。它们像一个一次性的、单向的通信管道。

  • std::promise:管道的写入端,位于生产者(子线程)中。

  • std::future:管道的读取端,位于消费者(主线程)中。

std::promise (承诺者 / 生产者)

它只有一个职责:在未来的某个时间点,往管道里放入一个值或一个异常

  • set_value(value): 成功完成任务,将结果放入管道。

  • set_exception(exception_ptr): 任务失败,将一个异常放入管道。

  • get_future(): 创建 promise 后,立即调用此方法获取其配对的 future 对象。

std::future (未来凭证 / 消费者)

它也只有一个职责:等待并获取管道里的值或异常。

  • get(): 等待直到管道里有东西,然后取出它。这个操作是阻塞的,并且只能调用一次

  • wait(): 只等待,不取值。

  • valid(): 检查这个 future 是否与一个通信管道关联。

代码示例:手动管理线程与结果传递
#include <iostream>
#include <thread>
#include <future>
#include <chrono>// 这个函数将在子线程中运行
// 它接收一个 promise 对象,用来在计算完成后设置结果
void compute_task(std::promise<int> p) {try {std::cout << "子线程开始计算..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));int result = 42;// 模拟可能发生的错误// throw std::runtime_error("计算失败!");// 计算完成,履行承诺,将结果放入 promisep.set_value(result);std::cout << "子线程已设置结果。" << std::endl;} catch (...) {// 如果发生异常,将异常信息放入 promisep.set_exception(std::current_exception());std::cout << "子线程已设置异常。" << std::endl;}
}int main() {// 1. 在主线程创建一个 promise 对象,类型为我们要返回的 intstd::promise<int> my_promise;// 2. 从 promise 中获取配对的 future 对象。这是我们的“取餐小票”std::future<int> result_future = my_promise.get_future();// 3. 创建子线程,并将 promise 的所有权转移(std::move)给它//    promise 不能被拷贝,只能被移动std::thread t(compute_task, std::move(my_promise));std::cout << "主线程正在做其他事情..." << std::endl;// ...可以执行其他不依赖于结果的任务...std::cout << "主线程现在需要计算结果了,开始等待..." << std::endl;try {// 4. 在需要结果时,调用 future 的 get() 方法//    如果子线程还没 set_value,这里会阻塞int final_result = result_future.get();std::cout << "主线程成功获取结果: " << final_result << std::endl;} catch (const std::exception& e) {// 如果子线程设置的是异常,get() 会重新抛出它std::cout << "主线程捕获到异常: " << e.what() << std::endl;}t.join();return 0;
}

4. std::async:自动挡,一键启动

std::async 是一个高级函数模板,它极大地简化了异步任务的创建。它就像前面比喻里的“全自动点餐机”,你只需要告诉它做什么,它会帮你处理好后台的一切,并直接给你“取餐小票”。

它封装了:

  1. 线程的创建与管理。

  2. promise 对象的创建。

  3. future 对象的创建和返回。

  4. 将任务的返回值或异常自动存入 promise 的逻辑。

代码示例:使用 async 简化任务
#include <iostream>
#include <future>
#include <chrono>// 这就是我们要异步执行的任务,它直接返回结果
int long_computation() {std::cout << "异步任务开始计算..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));// throw std::runtime_error("计算失败!"); // 异常也会被自动捕获return 42;
}int main() {// 1. 调用 std::async,传入要执行的函数//    它立即返回一个 future 对象,后台任务可能已经开始执行//    std::launch::async 策略确保任务在新线程中运行std::future<int> result_future = std::async(std::launch::async, long_computation);std::cout << "主线程正在做其他事情..." << std::endl;// ...std::cout << "主线程现在需要计算结果了,开始等待..." << std::endl;try {// 2. 在需要时,调用 future 的 get() 方法获取结果int final_result = result_future.get();std::cout << "主线程成功获取结果: " << final_result << std::endl;} catch (const std::exception& e) {std::cout << "主线程捕获到异常: " << e.what() << std::endl;}return 0;
}

可以看到,使用 std::async 的代码量大大减少,逻辑也更清晰。

5. 区别总结:我应该用哪个?

特性

std::promise / std::future (手动挡)

std::async (自动挡)

抽象层次

低层次,提供精细控制。

高层次,封装了所有细节。

线程管理

你必须手动创建和管理 std::thread

它为你自动管理线程 (或延迟执行)。

代码复杂度

更复杂,需要手动创建 promise,获取 future,移动 promise

非常简单,一行代码即可启动任务并获取 future

核心使用场景

当“设置结果”的逻辑与“启动任务”的逻辑分离时。例如,在一个复杂的事件驱动系统中,一个线程触发事件,另一个线程等待该事件并设置结果。或者当你需要自己管理一个线程池时。

绝大多数情况:你有一个函数,想让它在后台运行,并稍后获取其返回值。这是最常用、最推荐的方式。

异常处理

需要在子线程中 try-catch,并手动调用 p.set_exception()

自动处理。任务函数中的异常会被自动捕获并存储在 future 中。

关键决策点:
  • 如果你的需求仅仅是“异步调用一个函数并取回其返回值”,那么 99% 的情况下都应该使用 std::async。它更简单、更安全、意图更明确。

  • 只有当你需要非常精细地控制“承诺”何时以及如何被满足,或者你在自己实现一个线程池或消息队列,并且只需要一个纯粹的“值传递通道”时,才需要回退到使用 std::promisestd::future

http://www.dtcms.com/a/419826.html

相关文章:

  • 【MySQL 高阶】MySQL 程序详解
  • Cloudreve 性能卡顿?对接雨云对象存储,实现上传下载“满速飞”
  • 解锁机器人导航的全模态潜能!OmniVLA:机器人导航的全模态视觉-语言-动作模型
  • 制作一个买股票的网站怎么做网站空间与服务器
  • java-IO流-字节流
  • 为什么要学习C编程?
  • 外贸网站建设书籍东南亚营销型网站建设与网络推广
  • 烟台市政建设招标网站自己电脑做网站必须装jdk
  • 基于IMX6ULL芯片--I2C总线简单应用
  • 360网站卫士代备案流程推广员是什么工作
  • 特别分享:LangChain——构建强大LLM应用的“万能胶水”
  • 硬件开发2-ARM裸机开发3-I.MX6ULL - 时钟、定时器
  • Information Fusion | Modal-NexT:统一的多模态细胞数据整合
  • 医院信息化建设网站梵克雅宝手链
  • seo建站的步骤刷关键词排名
  • 初识网站开发流程图石家庄新闻发布会直播
  • 网站推广在哪些平台做外链微商刚起步怎么找客源
  • 怎样做化妆品公司网站wordpress 免费企业网站 模板下载
  • 禅道 v21.7.5 Docker 一键部署
  • 外国大气网站手机网站建设多钱
  • 数据库缓存双写一致性的实现方案
  • 做网站的需求调研深圳品牌营销咨询公司
  • 网站建设一般做什么网络营销方案设计心得
  • NXP MPC5777M LINFlexD 模块配置为 UART 模式详解(基于 PowerPC 架构)
  • 商务网站主页设计公司沈阳世纪兴网站制作
  • 织梦做网站主页容易吗怎么建立自己的网站平台多少钱
  • 新乡商城网站建设网站程序开发教程
  • 《计算》第七八章读书笔记
  • 全屏网站 内页怎么做网站搭建是什么专业学的
  • 现代企业网站建设特点如何学好网站建设