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

C++扩展 --- 并发支持库(补充3)

C++扩展 --- 并发支持库(补充2)https://blog.csdn.net/Small_entreprene/article/details/149854780?spm=1001.2014.3001.5501

C++ 异步编程:用 future 库优雅获取线程返回值

在多线程编程中,我们经常需要从线程中获取任务执行的结果。比如启动一个线程压缩文件后,我们需要知道压缩后的文件名和大小。在 C++11 之前,获取线程返回值并不直观,通常需要借助指针、互斥锁和条件变量的组合,代码复杂且容易出错。

传统方式的痛点

先看看没有 future 库时,我们是如何获取线程返回值的:

#include<iostream>
#include<thread>
#include<mutex>void fun(int x, int y, int* ans) {*ans = x + y;
}int main()
{int a = 10;int b = 8;int* sum = new int(0);std::thread t(fun, a, b, sum);t.join();// 获取线程的"返回值"std::cout << *sum << std::endl; // 输出:18delete sum;return 0;
}

这段代码虽然能工作,但存在明显问题:

  • 需要手动管理动态内存(new/delete)
  • 必须显式调用 join () 等待线程完成
  • 无法优雅地处理多个返回值
  • 没有异常处理机制

如果线程需要在不同阶段返回多个结果,代码会变得更加复杂,需要更多的同步机制来协调线程间的数据传递。

什么是 future 库?

C++11 引入的<future>头文件彻底改变了这种状况,它提供了一套完整的异步编程机制,让我们能以更简洁、安全的方式处理异步操作和获取结果。

简单来说,std::future就像是一张 "提货单":当你启动一个异步任务时,它会立即给你返回一个 future 对象,你可以在需要结果的时候,用这张 "提货单" 来获取任务的结果。如果任务还没完成,它会耐心等待;如果任务已经完成,就直接返回结果。

future 的核心组件

<future>库主要包含以下几个核心组件:

  1. std::future:获取异步操作结果的 "提货单"
  2. std::async:启动异步任务的便捷函数
  3. std::promise:用于主动设置异步操作结果
  4. std::packaged_task:封装可调用对象为异步任务

下面我们逐一了解这些组件的用法。

1. std::async:最简单的异步任务

std::async是启动异步任务最简便的方式,它会自动创建线程并运行任务,返回一个std::future对象供我们获取结果。

#include <iostream>
#include <future>
#include <thread>
#include <chrono>// 一个简单的任务函数,计算两数之和
int add(int a, int b) {// 模拟耗时操作std::this_thread::sleep_for(std::chrono::seconds(1));return a + b;
}int main() {std::cout << "主线程ID: " << std::this_thread::get_id() << std::endl;// 启动异步任务,立即返回future对象std::future<int> result = std::async(add, 10, 20);// 主线程可以继续做其他事情std::cout << "等待结果中..." << std::endl;// 获取结果,如果任务没完成会阻塞等待std::cout << "10 + 20 = " << result.get() << std::endl;return 0;
}

这段代码比传统方式简洁得多,我们不需要手动创建线程、管理内存,也不需要显式调用 join ()。

启动策略

std::async提供了两种启动策略:

  • std::launch::async:立即创建新线程执行任务
  • std::launch::deferred:延迟执行,直到调用 get () 时才在当前线程执行
#include <iostream>
#include <future>
#include <thread>
#include <chrono>void task(const char* name) {std::cout << name << " 开始执行,线程ID: " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << name << " 完成" << std::endl;
}int main() {std::cout << "主线程ID: " << std::this_thread::get_id() << std::endl;// 异步执行(新线程)auto f1 = std::async(std::launch::async, task, "异步任务");// 延迟执行(调用get时执行)auto f2 = std::async(std::launch::deferred, task, "延迟任务");std::cout << "主线程工作..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));f1.get();  // 等待异步任务完成f2.get();  // 此时才执行延迟任务return 0;
}

2. std::promise:主动设置结果

在 C++11 引入的并发编程模型中,std::promisestd::future是一对相辅相成的工具,它们共同构建了异步操作中 "结果传递" 的桥梁。简单来说,std::promise负责 "生产" 结果,而std::future负责 "消费" 结果,二者通过一个隐藏的 "共享状态" 实现跨线程通信。

先看一个基础示例,感受它们的协作方式:

#include <iostream>
#include <future>
#include <thread>
#include <chrono>int main() {// 创建promise与对应的future,二者绑定到同一共享状态std::promise<int> prom;std::future<int> fut = prom.get_future();// 启动线程执行异步任务,通过引用捕获promisestd::thread t([&prom]() {// 模拟耗时操作(如网络请求、文件IO等)std::this_thread::sleep_for(std::chrono::seconds(1));// 任务完成后,通过promise设置结果,触发共享状态就绪prom.set_value(42);  });// 主线程中,future等待结果就绪并获取std::cout << "等待异步结果..." << std::endl;// get()会阻塞直到结果可用,且只能调用一次std::cout << "获取到的结果: " << fut.get() << std::endl;t.join();return 0;
}

std::promise的独特价值

std::promise最强大的特性在于其 "主动性"—— 它允许我们在任意时机、任意线程中设置结果,而非局限于异步任务本身。这种灵活性使其适用于多种场景:

  • 分阶段任务:例如一个线程负责预处理数据,另一个线程在预处理完成后通过promise设置中间结果,主线程则通过future获取并继续处理。
  • 异常传递:当异步操作发生错误时,可通过promise.set_exception()将异常存储到共享状态,future.get()会在主线程中重新抛出该异常,实现跨线程异常安全传递。
  • 外部事件触发:比如等待用户输入、信号量触发等外部事件,完成后通过promise手动标记结果就绪。

std::future的核心区别

虽然二者总是成对出现,但职责边界清晰:

维度std::promisestd::future
角色结果的 "生产者"结果的 "消费者"
核心操作set_value()/set_exception()(设置结果)get()/wait()(获取 / 等待结果)
状态影响主动将共享状态置为 "就绪"被动等待共享状态变为 "就绪"
生命周期通常与生产者线程绑定通常与消费者线程绑定
复制性不可复制,仅可移动(确保结果唯一设置)不可复制,仅可移动(结果只能被获取一次)

需要注意的是,future.get()是一次性操作 —— 一旦调用,共享状态的结果会被 "取走",再次调用将导致未定义行为。如果需要在多个线程中获取同一结果,可使用std::shared_future(通过future.share()转换),它支持多次获取结果。

扩展:与其他异步工具的配合

std::promisestd::future是 C++ 异步编程的基础,它们还能与更高层次的工具配合使用:

  • std::packaged_task:将函数或可调用对象包装为一个 "任务",自动创建关联的future,本质是对promise的封装(任务执行完毕后自动调用set_value)。
  • std::async:更简洁的异步接口,可直接启动异步任务并返回future,无需手动管理线程和promise,底层可能使用线程池优化性能。

例如,用std::async简化上述示例:

// 自动管理线程,返回的future直接关联任务结果
std::future<int> fut = std::async(std::launch::async, []() {std::this_thread::sleep_for(std::chrono::seconds(1));return 42;
});
std::cout << "结果: " << fut.get() << std::endl;

综上,std::promisestd::future构成了 C++ 异步编程的 "结果通道",前者赋予我们主动控制结果的能力,后者则提供了安全获取结果的机制。理解这对工具的协作模式,是掌握 C++ 并发编程的重要一步。

3. std::packaged_task:封装任务

std::packaged_task 是 C++11 引入的异步编程工具,它能将可调用对象(函数、lambda、函数对象等)包装起来,自动创建关联的 std::future,当任务执行完毕后,结果会自动存储到共享状态中,供 future 获取。

基本语法结构

// 声明:包装返回类型为T,参数类型为Args...的可调用对象
std::packaged_task<T(Args...)> task(可调用对象);// 获取关联的future
std::future<T> fut = task.get_future();// 执行任务(两种方式)
task(args...);                  // 直接在当前线程执行
std::thread t(std::move(task), args...);  // 转移到新线程执行
#include <iostream>
#include <future>
#include <cmath>
#include <thread>// 计算平方根的函数
double compute_square_root(double x) {return std::sqrt(x);
}int main() {// 封装任务std::packaged_task<double(double)> task(compute_square_root);// 获取future对象std::future<double> result = task.get_future();// 在新线程中执行任务std::thread th(std::move(task), 25.0);// 获取结果std::cout << "25的平方根是: " << result.get() << std::endl;th.join();return 0;
}

std::packaged_task非常适合那些需要重复执行的任务,我们可以像传递普通函数一样传递它。

future 的常用方法

std::future提供了一系列方法来管理和获取异步操作的结果:

  • get():获取结果,如果任务未完成则阻塞等待
  • wait():等待任务完成,但不获取结果
  • wait_for(duration):等待指定时长,返回等待状态
  • wait_until(timepoint):等待到指定时间点,返回等待状态
  • valid():检查 future 是否有效(是否关联到一个异步任务)
#include <iostream>
#include <future>
#include <chrono>int long_task() {std::this_thread::sleep_for(std::chrono::seconds(3));return 42;
}int main() {auto fut = std::async(long_task);// 等待最多1秒auto status = fut.wait_for(std::chrono::seconds(1));if (status == std::future_status::ready) {std::cout << "任务已完成,结果: " << fut.get() << std::endl;} else if (status == std::future_status::timeout) {std::cout << "等待超时,任务仍在执行..." << std::endl;} else if (status == std::future_status::deferred) {std::cout << "任务被延迟执行" << std::endl;}// 最终还是要获取结果std::cout << "最终结果: " << fut.get() << std::endl;return 0;
}

异常处理

异步任务中抛出的异常会被 future 捕获,当调用get()时会重新抛出,让我们可以在主线程中统一处理异常:

#include <iostream>
#include <future>
#include <stdexcept>void risky_operation() {// 模拟一个可能失败的操作throw std::runtime_error("操作失败: 资源不足");
}int main() {std::future<void> fut = std::async(risky_operation);try {fut.get();  // 可能会抛出异常} catch (const std::exception& e) {// 在主线程中处理异常std::cout << "捕获到异常: " << e.what() << std::endl;}return 0;
}

这种机制确保了异步任务的异常不会被忽略,并且可以按照我们熟悉的方式处理。

总结

<future>库为 C++ 异步编程提供了强大而优雅的解决方案:

  1. 简化了线程返回值的获取方式,无需手动管理同步机制
  2. 提供了多种异步任务的创建方式(async、promise、packaged_task)
  3. 内置了灵活的等待机制和异常处理
  4. 让代码更加清晰、简洁、易于维护

从传统的线程 + 指针 + 锁的复杂组合,到使用 future 库的简洁代码,C++ 的异步编程体验得到了质的飞跃。掌握 future 库,能让你在多线程编程中更加得心应手,编写出更高质量的并发代码。

在实际开发中,我们可以根据具体需求选择合适的组件:简单异步任务用std::async,需要主动设置结果用std::promise,封装可重用任务用std::packaged_task

async 与 future 的关系?

std::async 和 std::future 是 “生产者 - 消费者” 般的配套关系:std::async 负责 “生产” 异步任务的结果,std::future 负责 “消费” 这个结果,二者必须配合使用才能完成异步任务的 “启动 - 获取结果” 全流程。

简单说就是:你用 std::async 启动任务时,它会立刻给你一个 std::future 对象;后续你要拿任务结果,就靠这个 std::future 对象来获取。

🔗 核心关系:std::async 是 std::future 的 “创建者”

std::async 是 C++ 提供的 异步任务启动工具,它的核心作用有两个:

  1. 启动一个异步任务(可能新建线程,也可能延迟执行);
  2. 自动创建并返回一个 std::future 对象,这个对象直接关联到该异步任务的结果。

你可以把它们的关系理解成 “外卖下单”:

  • 你用 std::async 就像 “下单点外卖”—— 发起一个需求(异步任务);
  • 平台返回的 “订单号” 就是 std::future—— 凭这个订单号,你能查进度(等待任务)、拿外卖(获取结果)。

看一段直观的代码就能明白:

// 1. 用 std::async 启动异步任务,它会返回一个 std::future 对象
std::future<int> fut = std::async([](){ // 异步任务:模拟耗时计算std::this_thread::sleep_for(1s);return 100; // 任务结果
});// 2. 用 std::future 对象获取结果(没完成就等,完成了就拿)
int result = fut.get(); 
std::cout << "任务结果:" << result << std::endl; // 输出 100

📌 为什么不能单独用?

二者谁也离不开谁,少了一个都无法完成 “异步获取结果” 的需求:

  • 只有 std::async,没有 std::future:你启动了任务,但拿不到结果 —— 就像下单后没要订单号,根本不知道怎么取外卖;
  • 只有 std::future,没有 std::async:你手里有个 “空的订单号”(默认构造的 std::future 是无效的),根本关联不到任何任务 —— 没法查进度、没法拿结果。

🆚 和其他 future 来源的区别

除了 std::asyncstd::future 还能通过 std::promise 和 std::packaged_task 创建,但 std::async 是最 “省心” 的方式:

来源(生产者)特点适用场景
std::async自动创建线程 / 管理任务,直接返回 future简单异步任务,不想手动管理线程
std::promise手动设置结果(set_value),需自己创建线程需要在任务中途 / 多个地方控制结果
std::packaged_task封装可调用对象(函数 /lambda),需自己传线程需重复使用任务逻辑,或手动管理线程池

但无论哪种来源,std::future 的角色都是固定的 ——异步结果的 “接收者” 和 “访问接口”

std::async 与 std::future 关系对比表

该表格从职责定位、核心能力、配合流程等维度,清晰梳理二者的关联与区别,帮你快速掌握它们的协作逻辑。

对比维度std::async(异步任务启动器)std::future(异步结果接收器)二者关联说明
核心职责发起异步任务,是结果的 “生产者”存储 / 获取异步结果,是结果的 “消费者”生产者与消费者的配套关系,缺一不可
创建方式直接调用函数(如 std::async(任务函数)由 std::async/std::promise/std::packaged_task 自动生成不能手动创建有效实例,必须通过其他组件 “赠送”
核心能力1. 启动任务(支持立即 / 延迟两种策略)2. 自动绑定任务与 future3. 隐式管理线程生命周期1. get():阻塞获取结果(仅能调用一次)2. wait():阻塞等待任务完成(不拿结果)3. wait_for():限时等待并返回状态async 启动任务后,必须通过其返回的 future 才能拿到结果
配合流程步骤 1:调用 std::async 传入任务,生成 future步骤 2:async 内部启动 / 托管任务执行步骤 3:调用 future 的 get()/wait() 等待结果步骤 4:获取任务返回值或捕获异常流程环环相扣,少一步都无法完成异步结果获取
生命周期任务执行完后自动结束(或随主线程退出)调用 get() 后变为无效(valid() 返回 false)future 失效后不可再用,async 任务完成后也无法重启
常见错误1. 忽略返回的 future(任务会被阻塞执行)2. 重复调用启动同一任务1. 对无效 future 调用 get()(崩溃)2. 多次调用 get()(崩溃)

promise 是不是封装了 future ?

实际上std::promisestd::future并非 "封装" 关系,而是互补的协作关系—— 它们就像一根管道的两端:promise是写入端,future是读取端,共同操作同一个 "共享状态"(shared state)。

可以用一个生活场景类比:

  • 你(主线程)让朋友(子线程)去买咖啡,递给朋友一个空杯子(promise
  • 朋友买到咖啡后,把咖啡倒进杯子里(promise.set_value()
  • 你拿着杯子的 "取货凭证"(future),等朋友把咖啡装好后,通过凭证拿到咖啡(future.get()

这里的关键是:杯子(共享状态)才是核心载体promisefuture只是操作这个载体的两个接口。

从实现角度看二者的关系

C++ 标准并未规定promisefuture的具体实现细节,但逻辑上它们的关系是这样的:

+------------------------+
|      共享状态          |
|  (存储结果/异常/状态)   |
+----------+-------------+|+-------+-------+|               |
+--v--+         +--v--+
|promise|       |future|
|(写接口)|       |(读接口)|
+------+         +------+
  • promise拥有对共享状态的 "写权限"(设置结果 / 异常)
  • future拥有对共享状态的 "读权限"(获取结果 / 异常)
  • 两者通过promise.get_future()建立关联,形成唯一的读写对

为什么容易误解为 "封装"?

可能是因为std::packaged_taskstd::async这类工具内部封装了 promise,让我们无需直接操作它:

// packaged_task内部包含了一个promise
std::packaged_task<int()> task([]{ return 42; });
std::future<int> fut = task.get_future(); // 这里的future依然关联内部promise的共享状态

但这是更高层工具对 promise 的封装,而非future封装promise。本质上,promisefuture始终是平等的协作关系,谁也不包含谁。

这种设计的精妙之处在于解耦生产者和消费者

  • 生产者只需知道如何通过promise设置结果,无需关心谁会使用这个结果
  • 消费者只需知道如何通过future获取结果,无需关心结果由谁产生
  • 两者甚至可以在不同的线程、不同的时间点创建,只要能关联到同一个共享状态即可

理解这种 "读写分离" 的设计,就能更清晰地把握 C++ 异步编程的核心思想了。

后面我们可以结合这些知识语法,自己实现一个案例!

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

相关文章:

  • 高级建站网站十大装饰公司排行榜
  • 苏州专门网站网络架构有几种模式
  • 深度学习任务
  • 网站建设思路及设计方案wordpress如何汉化
  • 营销网站建设设计菏泽 网站建设公司
  • 做素材类的网站赚钱吗青岛胶南做网站的有多少
  • 如何查询网站是否有做404商业模式包括哪些模式
  • 网站被降权表现佛山网站专家
  • 重庆做网站制作的公司做网站去哪找客户
  • 土地 水利 勘测设计 公司宣传册设计样本江门当地的免费网站优化
  • “常小豚苏超限定款”公益联名:以热爱之名守护生态赛场
  • 深圳网站建设设计首选公司搜索关键词排名推广
  • 怎么把自己网站推广出去在线域名注册
  • 贵州网站备案查询ftp跟网络连接Wordpress
  • 南京手机网站制作网络营销成功案例3篇
  • wordpress 多站点主题精准信息预测
  • 高端网站自己怎么做网页
  • 【香橙派开发笔记】中文界面与输入法配置
  • 做网站虚拟主机哪家好房地产网站建设招商
  • 大气的企业网站源码新西兰网站开发专业
  • 上海模板网站建站易网网站
  • 二手书网站建设的目的建设银行官方网站地址
  • Confluence API 常用操作的 Python 示例集合
  • 哪些域名不能够做淘宝客网站做内贸哪个网站找客户
  • 违禁网站用什么浏览器seo的优化策略有哪些
  • 手把手教你做网站 3wix做的免费网站可以用吗
  • 做网站建设网站制作wordpress需注册访问
  • 宿迁做百度网站地点安卓app开发多少钱
  • 安防监控网站模板邢台太行中学怎么样
  • 网站的设计亮点中国纳溪门户网站建设项目环境影响