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

大家好,我是小康。

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

前言:为什么要学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++黑魔法」future与promise:不加锁的异步编程,原来可以这么简单!

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

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

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

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

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

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

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

发表评论

评论已关闭。

相关文章