大家好,我是小康。

你是否曾经为了让程序同时做多件事而绞尽脑汁?是否被多线程编程的各种锁、条件变量搞得头昏脑胀?今天,我要告诉你一个秘密武器,让你轻松驾驭异步编程的海洋!

前言:为什么要学future和promise?

朋友,想象一下这个场景:你在餐厅点了一份需要20分钟才能做好的复杂菜品。你有两个选择:

  1. 坐在那里盯着厨房门口,等待20分钟(同步等待)
  2. 服务员给了你个取餐码,菜品好了会通知你,同时你可以刷刷手机或聊聊天(异步等待)

显然,第二种方式更高效,对吧?

在C++编程中,futurepromise就像是这个"取餐码+通知"系统,让你的程序能够优雅地处理异步任务。它们是C++11引入的现代并发编程工具,比传统的线程、互斥锁和条件变量更加简单易用。

一、异步任务是个啥?通俗地说就是"后台运行"

在解释futurepromise之前,我们先聊聊什么是异步任务。

异步任务就是指那些可以在"后台"执行,不需要主线程等待的任务。比如:

  • 下载一个大文件
  • 复杂计算(如图像处理)
  • 访问远程服务器

想象一下你的电脑在下载游戏的同时,你还能继续刷视频、聊天,这就是异步的魅力!

二、future:未来会得到的结果

future可以理解为"未来的结果",它就像一张电影票根:

  • 你现在拿着票根(future)
  • 电影(异步任务)正在后台准备中
  • 当电影准备好了,你可以用票根进场(获取结果)

用代码说话:

#include <iostream>
#include <future>
#include <thread>
#include <chrono> int compute_answer() {
// 假装这是个复杂计算
std::cout << "开始计算终极问题的答案..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
std::cout << "计算完成!" << std::endl;
return 42; // 返回结果
} int main() {
// 启动异步任务,立即返回一个future
std::cout << "主线程:启动一个耗时任务" << std::endl;
std::future<int> answer_future = std::async(compute_answer); std::cout << "主线程:哇,不用等待,我可以继续做其他事情!" << std::endl; // 做一些其他工作...
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "主线程:我在异步任务计算的同时做了些其他事" << std::endl; // 当需要结果时,我们可以获取它
// 如果结果还没准备好,这会阻塞直到结果可用
std::cout << "主线程:好了,现在我需要知道答案了,等待结果..." << std::endl;
int answer = answer_future.get(); std::cout << "终极答案是:" << answer << std::endl; return 0;
}

输出结果

主线程:启动一个耗时任务
开始计算终极问题的答案...
主线程:哇,不用等待,我可以继续做其他事情!
主线程:我在异步任务计算的同时做了些其他事
计算完成!
主线程:好了,现在我需要知道答案了,等待结果...
终极答案是:42

看到了吗?主线程启动了计算,但并不立即等待结果,而是继续执行其他代码。只有当真正需要结果时(调用get()),才会等待异步任务完成。

三、promise:我保证会给你结果

如果说future是领取结果的凭证,那么promise就是一个承诺:"我保证会在某个时刻设置一个值"。它们是一对好搭档:

  • promise负责在某个时刻设置结果
  • future负责在需要时获取结果

这就像你和朋友的约定:

  • 你:我承诺会告诉你考试成绩(promise)
  • 朋友:我会等你告诉我(future)

来看个例子:

#include <iostream>
#include <future>
#include <thread>
#include <chrono> void producer(std::promise<int> my_promise) {
std::cout << "生产者:我要开始生产一个重要的值了..." << std::endl; // 假装我们在做一些复杂的计算
std::this_thread::sleep_for(std::chrono::seconds(2)); int result = 42;
std::cout << "生产者:计算完成,设置结果到promise" << std::endl; // 设置promise的值,这会通知相关的future
my_promise.set_value(result);
} int main() {
// 创建一个promise
std::promise<int> answer_promise; // 从promise获取一个future
std::future<int> answer_future = answer_promise.get_future(); // 启动一个线程,传入promise
std::cout << "主线程:启动生产者线程" << std::endl;
std::thread producer_thread(producer, std::move(answer_promise)); // 主线程继续做其他事情
std::cout << "主线程:我可以做自己的事,不用等待..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1)); // 当需要结果时
std::cout << "主线程:现在我需要结果了,等待future..." << std::endl;
int answer = answer_future.get();
std::cout << "主线程:收到结果:" << answer << std::endl; // 别忘了等待线程结束
producer_thread.join(); return 0;
}

输出结果

主线程:启动生产者线程
生产者:我要开始生产一个重要的值了...
主线程:我可以做自己的事,不用等待...
主线程:现在我需要结果了,等待future...
生产者:计算完成,设置结果到promise
主线程:收到结果:42

这个例子展示了如何使用promisefuture在线程间传递结果。生产者线程通过promise设置值,主线程通过future获取值。

四、future的几种获取方式

除了通过promise获取future,C++11还提供了其他便捷方式:

1. 通过async获取future

std::async是最简单的方式,它自动创建线程并返回future

std::future<int> result = std::async([]() {
return 42;
});

2. 通过packaged_task获取future

std::packaged_task包装了一个可调用对象,并允许你获取其future

#include <iostream>
#include <future>
#include <thread> int main() {
// 创建一个packaged_task,包装一个lambda函数
std::packaged_task<int(int, int)> task([](int a, int b) {
std::cout << "计算 " << a << " + " << b << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时计算
return a + b;
}); // 获取future
std::future<int> result = task.get_future(); // 在新线程中执行任务
std::thread task_thread(std::move(task), 10, 32); // 主线程做其他事情...
std::cout << "主线程:等待计算结果..." << std::endl; // 获取结果
int sum = result.get();
std::cout << "结果是:" << sum << std::endl; task_thread.join();
return 0;
}

输出结果

主线程:等待计算结果...
计算 10 + 32
结果是:42

五、实用功能:future的超时等待

有时候,我们不想无限期地等待异步任务。future提供了带超时的等待功能:

#include <iostream>
#include <future>
#include <chrono> int long_calculation() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
} int main() {
auto future = std::async(std::launch::async, long_calculation); // 设置1秒超时
auto status = future.wait_for(std::chrono::seconds(1)); if (status == std::future_status::ready) {
std::cout << "任务已完成,结果是:" << future.get() << std::endl;
} else if (status == std::future_status::timeout) {
std::cout << "等待超时!任务还没完成" << std::endl; // 我们仍然可以继续等待完成
std::cout << "继续等待..." << std::endl;
std::cout << "最终结果:" << future.get() << std::endl;
} return 0;
}

输出结果

等待超时!任务还没完成
继续等待...
最终结果:42

六、异常处理:当异步任务出错时

异步任务中的异常会被捕获并存储在future中,当你调用get()时会重新抛出:

#include <iostream>
#include <future>
#include <stdexcept> int may_throw() {
std::this_thread::sleep_for(std::chrono::seconds(1));
throw std::runtime_error("哎呀,出错了!");
return 42;
} int main() {
auto future = std::async(may_throw); try {
int result = future.get();
std::cout << "结果:" << result << std::endl;
} catch (const std::exception& e) {
std::cout << "捕获到异常:" << e.what() << std::endl;
} return 0;
}

输出结果

捕获到异常:哎呀,出错了!

这种设计非常优雅——无论异步任务是成功返回值还是抛出异常,都能通过同一个future接口处理。

七、实际应用案例:并行计算求和

让我们用一个更实用的例子来巩固理解:并行计算大数组的和。

#include <iostream>
#include <vector>
#include <numeric>
#include <future>
#include <chrono> // 计算数组部分和的函数
long long partial_sum(const std::vector<int>& data, size_t start, size_t end) {
return std::accumulate(data.begin() + start, data.begin() + end, 0LL);
} int main() {
// 创建一个大数组
const size_t size = 100000000; // 1亿个元素
std::vector<int> data(size, 1); // 全是1的数组 auto start_time = std::chrono::high_resolution_clock::now(); // 单线程计算
long long single_result = std::accumulate(data.begin(), data.end(), 0LL); auto single_end = std::chrono::high_resolution_clock::now();
auto single_duration = std::chrono::duration_cast<std::chrono::milliseconds>(single_end - start_time); std::cout << "单线程结果: " << single_result << " (耗时: "
<< single_duration.count() << "ms)" << std::endl; // 使用4个线程并行计算
auto multi_start = std::chrono::high_resolution_clock::now(); const size_t num_threads = 4;
const size_t block_size = size / num_threads; std::vector<std::future<long long>> futures; for (size_t i = 0; i < num_threads; ++i) {
size_t start = i * block_size;
size_t end = (i == num_threads - 1) ? size : (i + 1) * block_size; // 启动异步任务
futures.push_back(std::async(std::launch::async,
partial_sum, std::ref(data), start, end));
} // 收集结果
long long multi_result = 0;
for (auto& f : futures) {
multi_result += f.get();
} auto multi_end = std::chrono::high_resolution_clock::now();
auto multi_duration = std::chrono::duration_cast<std::chrono::milliseconds>(multi_end - multi_start); std::cout << "多线程结果: " << multi_result << " (耗时: "
<< multi_duration.count() << "ms)" << std::endl; std::cout << "加速比: " << static_cast<double>(single_duration.count()) / multi_duration.count() << "x" << std::endl; return 0;
}

可能的输出结果(取决于你的硬件):

单线程结果: 100000000 (耗时: 570ms)
多线程结果: 100000000 (耗时: 171ms)
加速比: 3.33333x

看到没?多线程版本明显更快!这正是future的价值所在——让并行编程变得简单而高效。

八、总结:为什么future和promise这么香?

现在,你已经了解了C++11中futurepromise的基本用法。它们的优势在于:

  1. 简化异步编程:比直接管理线程、互斥锁和条件变量简单得多
  2. 清晰的所有权模型promise负责生产值,future负责消费值
  3. 异常传递:异步任务中的异常会自动传递给等待的future
  4. 超时控制:可以设置等待超时,避免无限阻塞
  5. 与现代C++完美融合:配合lambda、智能指针等现代特性使用更加优雅

记住这个类比就行:promise就像一个"承诺给你结果的人",future就像"等待结果的凭证"。

下次当你需要在程序中执行耗时操作又不想阻塞主线程时,就想到futurepromise吧!它们会让你的代码更加现代、高效,还能充分利用多核处理器的威力。

最后一个小提示:虽然C++11的futurepromise已经很强大,但如果你追求更高级的异步编程,可以考虑看看C++20的协程(coroutine)特性,那又是另一个让人兴奋的话题了~


写在最后:这只是异步编程旅程的开始...

嘿,朋友!看到这里,你已经掌握了C++异步编程的一把利器了!但编程世界就像宇宙一样浩瀚无边,我们的探索才刚刚开始~

学到了新知识?有疑问?有心得?

快到评论区和大家分享吧!我会亲自解答你的每一个问题。

如果这篇文章让你有所收获...

请点赞、收藏、关注哦,这对我来说,就像异步任务完成时收到的一个promise.set_value()一样珍贵!转发给你的程序猿/媛朋友,他们一定也会感谢你分享这个编程"黑魔法"!

想要更多编程"神器"吗?

关注我的公众号「跟着小康学编程」,这里有:

多线程编程实战秘籍 —— 让你的程序飞起来,CPU核心全利用!

C++面试题深度解析 —— 面试官:这水平,必须给offer!

现代C++高级技巧 —— C++11/14/17/20新特性全解析,代码立减50%!

STL源码探秘 —— 看大神是怎么写代码的,学就要学最好的!

实战项目分享 —— 纸上得来终觉浅,实战出真知!

扫码即可关注

哈,想不想和一群志同道合的C++爱好者一起交流?

扫描下方二维码,加入我们的技术交流群!

群里有什么?有大厂C++开发工程师坐镇,有热心的同行互帮互助...

遇到 bug 卡住了?代码性能优化不上去?面试准备没方向?

别着急,一个消息发出去,群里的大佬们立马帮你解决!

记住,在编程的世界里,你从不孤单。我们的future已经连接,就等你的get()来获取知识的果实了!

「C++黑魔法」future与promise:不加锁的异步编程,原来可以这么简单!的更多相关文章

  1. Promise是如何实现异步编程的?

    Promise标准 不能免俗地贴个Promise标准链接Promises/A+.ES6的Promise有很多方法,包括Promise.all()/Promise.resolve()/Promise.r ...

  2. 「SSH 黑魔法」: 代理、端口转发和 shell 共享

    在好朋友的推荐下,我看了这个视频: The Black Magic Of SSH 这个视频里面,介绍了  ssh 的一些高级应用:结合工作的经历,两类问题会对我们平时的工作帮助很大: 1. 两个人怎么 ...

  3. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

  4. 微服务架构之「 API网关 」

    在微服务架构的系列文章中,前面已经通过文章<架构设计之「服务注册 」>介绍过了服务注册的原理和应用,今天这篇文章我们来聊一聊「 API网关 」. 「 API网关 」是任何微服务架构的重要组 ...

  5. ES6笔记(7)-- Promise异步编程

    系列文章 -- ES6笔记系列 很久很久以前,在做Node.js聊天室,使用MongoDB数据服务的时候就遇到了多重回调嵌套导致代码混乱的问题. JS异步编程有利有弊,Promise的出现,改善了这一 ...

  6. 简述异步编程&Promise&异步函数

    前言:文章由本人在学习之余总结巩固思路,不足之前还请指出. 一.异步编程 首先我们先简单来回顾一下同步API和异步API的概念 1.同步API:只有当前的API执行完成之前,才会执行下一个API 例: ...

  7. 笑了,面试官问我知不知道异步编程的Future。

    荒腔走板 大家好,我是 why,欢迎来到我连续周更优质原创文章的第 60 篇. 老规矩,先来一个简短的荒腔走板,给冰冷的技术文注入一丝色彩. 上面这图是我五年前,在学校宿舍拍的. 前几天由于有点事情, ...

  8. 【vuejs面试题】务必熟知的vuejs面试题「务必收藏」

    如果能帮到你,点个赞吧,务必熟知的vuejs面试题「务必收藏」 vuejs 基础必备 1.active-class 是哪个组件的属性?嵌套路由怎么定义 (1).active-class 是 vue-r ...

  9. 「建议心心」要就来15道多线程面试题一次爽到底(1.1w字用心整理)

    . 本文是给**「建议收藏」200MB大厂面试文档,整理总结2020年最强面试题库「CoreJava篇」**写的答案,所有相关文章已经收录在码云仓库:https://gitee.com/bingqil ...

  10. 前端构建工具之gulp(一)「图片压缩」

    前端构建工具之gulp(一)「图片压缩」 已经很久没有写过博客了,现下终于事情少了,开始写博吧 今天网站要做一些优化:图片压缩,资源合并等 以前一直使用百度的FIS工具,但是FIS还没有提供图片压缩的 ...

随机推荐

  1. Flink运行时架构

    一.运行时的组件和基本原理 1.作业管理器 (1)控制一个应用程序执行的主进程,也就是说,每个应用程序都会被一个不同的JobManager所控制执行. (2)JobManager会先接收到要执行的应用 ...

  2. idea中启动web、jsp项目

    1. idea打开项目 选择要打开的项目的根目录 2. 项目配置 配置jdk modules配置 添加web 添加依赖 删除爆红的依赖 添加依赖目录或者jar 配置web.xml 配置lib 如果没有 ...

  3. C# 网页截图全攻略:三种技术与 Chrome 路径查找指南

    全局配置 string url = "https://blog.csdn.net/sunshineGGB/article/details/122316754"; 一.Puppete ...

  4. Keepalived学习,双主热备高可用

    双主热备可以看做双机主备的升级(双机主备链接 https://www.cnblogs.com/hmxs/p/12041735.html),它是为了让两台设备都能提供服务,而不是主节点正常时,备用节点一 ...

  5. 当我老丈人都安装上DeepSeek的时候,我就知道AI元年真的来了!

    关注公众号回复1 获取一线.总监.高管<管理秘籍> 春节期间DeepSeek引爆了朋友圈,甚至连我老丈人都安装了APP,这与两年前OpenAI横空出世很不一样,DeepSeek似乎真的实现 ...

  6. 有限元方法[Matlab]-笔记

    <-- 访问笔记代码仓库 --> << MATLAB Codes for Finite Element Analysis - Solids and Structures (Fe ...

  7. centos 防火墙配置,并限制端口

    查看防火墙状态 systemctl status firewalld 如果防火墙处于停止状态,则启动它: systemctl start firewalld 并设置防火墙开机自启: systemctl ...

  8. Laravel MongoDB

    Laravel MongoDB This package adds functionalities to the Eloquent model and Query builder for MongoD ...

  9. 1、从DeepSeek API调用到Semantic Kernel集成:深度解析聊天机器人开发全链路

    引言:AI时代下的聊天机器人开发范式演进 在生成式AI技术爆发的当下,基于大语言模型(LLM)的聊天机器人开发已形成标准化技术链路.本文将结合DeepSeek API与微软Semantic Kerne ...

  10. SpringBoot+Nginx大文件传输

    Nginx配置 # 公众端的附件上传 location /api/visitor/upload { # Pass altered request body to this location uploa ...