文章目录
C++ 11 异步操作 std::future
以下是关于 C++ 标准库 <future>
的详细整理,包含核心概念、组件功能、使用场景及扩展说明:
一、 概述
- 引入目的:C++11 引入了 头文件,它提供了一种异步编程的机制,允许程序在等待某个操作完成时继续执行其他任务。 库是 C++ 标准库中并发编程的一部分,它允许程序员以一种更简洁和安全的方式处理异步操作。
- 核心思想:通过 异步任务 与 结果通信机制 解耦,允许主线程在等待异步操作完成时执行其他任务。
- 应用场景:耗时计算(如文件 I/O、网络请求)、多任务并行处理、分布式系统中的任务调度等。
二、核心组件与功能
1. std::future
- 作用:表示异步操作的最终结果,提供访问异步操作状态(是否完成、是否异常)和获取结果的接口。注意future并不能单独使用,需要搭配一些能够执行异步任务的模板类或函数一起使用。比如std::async函数模板,std::packaged_task类模板
- 关键方法:
get()
:阻塞当前线程,等待异步操作完成并获取结果;若操作抛出异常,get()
会重新抛出。wait()
:阻塞当前线程,仅等待异步操作完成(不获取结果)。wait_for()
/wait_until()
:设定超时时间等待操作完成,返回std::future_status
状态(ready
/timeout
/deferred
)。
- 特性:
- 移动语义:
std::future
对象不可拷贝,但可移动(std::move
),避免资源竞争。 - 延迟执行:配合
std::async
的std::launch::deferred
策略,可延迟到首次调用get()
或wait()
时执行任务。
- 移动语义:
2. std::promise
-
作用:实例化的对象可以返回一个future,在其他线程中向promise对象设置数据,其他线程的关联 future 就可以获取数据,用于手动设置异步操作的结果(值或异常)。
-
关键方法:
set_value(value)
:设置异步操作的成功结果。set_exception(exception_ptr)
:设置异步操作的异常结果。
-
使用模式
- 先实例化一个指定结果的promise对象
- 通过promise对象,获取关联的future对象
- 在任意位置给promise设置数据,就可以通过关联的future获取到这个设置的数据了
#include<iostream> #include<future> #include<thread> #include<memory> int Add(int num1,int num2){ return num1 + num2; } int main(){ std::promise<int> prom; //实例化一个promise对象 std::future<int> result = prom.get_future(); //获取promise对象关联的future对象 std::thread thr([&prom](){ prom.set_value(Add(11,22)); //设置promise对象的值 }); std::cout<<result.get()<<std::endl; thr.join(); return 0; }
3. std::packaged_task
-
作用:为一个函数生成一个异步封装可调用对象(函数、Lambda、仿函数),用于在其他线程中执行,使其异步执行并将结果存储到
std::future
中。 -
关键特性:
- 模板参数:
std::packaged_task<Ret(Args...)>
,指定封装函数的返回类型Ret
和参数类型Args...
。 - 绑定参数:通过
std::placeholders
或 Lambda 绑定固定参数,实现部分应用(Partial Application)。
- 模板参数:
-
示例:
#include <iostream> #include <future> #include <thread> int Add(int num1,int num2){ std::cout << "Add函数开始执行" << std::endl; return num1 + num2; } int main(){ //1.封装对象 auto task = std::make_shared<std::packaged_task<int(int,int)>>(Add); //创建一个packaged_task对象,packaged_task对象可以包装一个函数,并返回一个future对象 //packaged_task对象可以异步执行函数 //创建一个智能指针对象,避免拷贝构造 //2.获取任务包关联的future对象 std::future<int> result = task->get_future(); //3.执行任务 std::thread thr([task](){ (*task)(11,22); }); // 添加这行等待线程完成 thr.join(); //4.获取结果 std::cout << "result: " << result.get() << std::endl; return 0; }
输出结果:
Add函数开始执行
result: 33
4. std::async
-
作用:简化异步任务的启动,异步执行一个函数,返回一个future对象用于获取函数结果。
-
语法:
template <class Function, class... Args> std::future<typename std::result_of<Function(Args...)>::type> async(launch policy, Function&& f, Args&&... args);
-
参数说明:
policy:执行策略(可选):
std::launch::async
:强制在新线程中异步执行。std::launch::deferred
:延迟执行(在首次调用get()
/wait()
时执行,可能在当前线程)。- 省略
policy
:由编译器决定(async
或deferred
)。
-
优势:无需手动管理线程和
std::promise
,适合快速创建简单异步任务。 -
代码示例:
#include <iostream> #include <future> #include <thread> int Add(int num1,int num2){ //以异步执行加法运算为例子 std::cout << "Add函数开始执行" << std::endl; return num1 + num2; } int main(){ std::future<int> result = std::async(std::launch::async,Add,11,22); //创建一个异步任务,异步任务执行Add函数,参数为11和22 //返回值是一个future对象,future对象可以获取异步任务的返回值 //进行了一个异步非阻塞调用 //如果将async换成deferred,则不会立即执行Add函数,而是等到get函数被调用时才会执行Add函数 //std::future<int> result = std::async(std::launch::deferred,Add,11,22); std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout<<"----------------------------------\n"; //get函数用于获取异步任务的返回值,如果还没有结果就会阻塞 std::cout << "result: " << result.get() << std::endl; return 0; }
运行结果:
Add函数开始执行
result: 33
三、异常处理
-
机制:异步操作中抛出的异常会被包装为
std::exception_ptr
,通过std::promise
或std::packaged_task
传递给std::future
,最终在调用get()
时重新抛出。 -
示例:
std::future<int> fut = std::async([]() { throw std::runtime_error("Async error"); // 异步线程中抛出异常 }); try { int result = fut.get(); // 此处重新抛出异常 } catch (const std::exception& e) { std::cout << "Caught: " << e.what() << std::endl; // 输出 "Caught: Async error" }
四、高级特性与扩展
1. std::shared_future
-
作用:多个
std::shared_future
可共享同一个异步操作的结果,支持拷贝(普通std::future
仅支持移动)。 -
创建方式:通过
std::future share()
方法转换:
std::future<int> fut = std::async([]() { return 42; }); std::shared_future<int> shared_fut = fut.share(); // 转换为可共享的 future
2. 异步任务组合(C++20 扩展)
-
引入背景:C++20 新增
<execution>
头文件,结合<future>
提供更强大的任务组合能力(如then
、when_all
、when_any
等)。 -
示例(链式调用)
#include <future> #include <execution> auto task = std::async(std::launch::async, []() { return 10; }).then([](std::future<int> f) { // 链式调用 int x = f.get(); return x * 2; }); int result = task.get(); // 输出 20
五、注意事项
- 线程管理:
- 使用
std::thread
时需确保线程生命周期不早于std::future
,避免悬空引用(可通过join()
或detach()
管理)。 std::async
会自动管理线程,但需注意deferred
策略可能在主线程执行。
- 使用
- 移动语义:
std::future
和std::packaged_task
不可拷贝,必须通过std::move
转移所有权。
- 避免阻塞:
- 避免在 UI 线程或关键路径上调用
get()
或wait()
,防止界面卡顿或死锁。
- 避免在 UI 线程或关键路径上调用
- 内存管理:
- 异步任务若持有资源(如指针),需确保资源在任务执行期间有效(可结合智能指针
std::shared_ptr
)。
- 异步任务若持有资源(如指针),需确保资源在任务执行期间有效(可结合智能指针
六、总结
组件 | 核心功能 |
---|---|
std::future | 存储异步结果,提供阻塞式获取结果和查询状态的接口。 |
std::promise | 手动设置异步结果(值或异常),与 std::future 配对使用。 |
std::packaged_task | 封装可调用对象,异步执行并将结果存入 std::future 。 |
std::async | 便捷式启动异步任务,自动管理线程和 std::future 。 |
std::shared_future | 支持拷贝的 future ,允许多个对象共享异步结果。 |