【C++多线程】std::async和std::future
在C++中,std::async
和 std::future
是C++11引入的并发编程工具,主要用于处理异步任务和线程间通信。它们配合使用,可以简化多线程编程,尤其是需要异步执行任务并获取结果的场景。以下是对 std::async
和 std::future
的详细介绍,包括定义、用法、特性、常见场景和注意事项。
1. std::async
定义
std::async
是一个函数模板,用于启动一个异步任务。它会返回一个 std::future
对象,通过这个对象可以获取任务的结果或状态。std::async
的核心思想是将任务的执行与调用线程解耦,允许任务在后台运行(可能在新线程中,也可能是延迟执行)。
语法
template <class Fn, class... Args>
std::future<std::invoke_result_t<Fn, Args...>> async(std::launch policy, Fn&& fn, Args&&... args);
policy
:指定任务的启动策略(见下文)。fn
:要执行的函数或可调用对象。args
:传递给fn
的参数。- 返回值:一个
std::future
对象,用于获取任务结果。
启动策略(std::launch
)
std::async
的第一个参数是一个 std::launch
枚举值,控制任务的执行方式:
std::launch::async
:立即在新线程中异步执行任务。std::launch::deferred
:延迟执行任务,直到future
的get()
或wait()
被调用(在调用线程中同步执行)。std::launch::async | std::launch::deferred
(默认):实现决定是立即异步执行还是延迟执行。
如果不指定 policy
,默认是 std::launch::async | std::launch::deferred
,具体行为取决于编译器和运行时环境。
示例
#include <iostream>
#include <future>
int compute() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时任务
return 42;
}
int main() {
// 使用 std::launch::async 强制异步执行
std::future<int> fut = std::async(std::launch::async, compute);
std::cout << "主线程继续执行...\n";
int result = fut.get(); // 等待任务完成并获取结果
std::cout << "结果: " << result << "\n";
return 0;
}
输出
主线程继续执行...
结果: 42
compute
在一个新线程中运行,主线程无需等待即可继续执行。fut.get()
阻塞直到结果可用。
2. std::future
定义
std::future
是一个类模板,表示一个异步操作的未来结果。它通常与 std::async
、std::promise
或 std::packaged_task
配合使用,提供了一种机制来获取异步任务的结果或状态。
主要方法
-
get()
获取异步任务的结果。如果结果未准备好,会阻塞当前线程;如果任务抛出异常,则重新抛出。- 返回类型:任务返回值的类型。
- 注意:只能调用一次,第二次调用是未定义行为。
-
wait()
等待任务完成,但不返回结果。不会抛出异常。 -
wait_for(duration)
等待指定时间,返回std::future_status
:std::future_status::ready
:任务已完成。std::future_status::timeout
:超时。std::future_status::deferred
:任务被延迟执行。
-
wait_until(time_point)
类似于wait_for
,但指定一个绝对时间点。 -
valid()
检查future
是否有效(即是否关联了一个异步任务)。
特性
- 不可复制:
std::future
只能移动(std::move
),确保结果的所有权唯一。 - 一次性:
get()
调用后,future
变为无效状态。
3. std::async
和 std::future
的配合
std::async
返回的 std::future
是获取异步任务结果的关键。以下是一个更详细的例子:
#include <iostream>
#include <future>
#include <chrono>
int slow_add(int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟慢速计算
return a + b;
}
int main() {
// 异步执行 slow_add
std::future<int> fut = std::async(std::launch::async, slow_add, 3, 4);
std::cout << "等待结果...\n";
// 检查状态
auto status = fut.wait_for(std::chrono::seconds(1));
if (status == std::future_status::timeout) {
std::cout << "任务还未完成,继续等待...\n";
fut.wait(); // 继续等待直到完成
}
int result = fut.get();
std::cout << "结果: " << result << "\n";
return 0;
}
输出
等待结果...
任务还未完成,继续等待...
结果: 7
slow_add
在新线程中运行,耗时 2 秒。- 主线程用
wait_for
检查 1 秒后任务未完成,然后用wait
等待剩余时间,最后通过get
获取结果。
4. 常见使用场景
(1)异步计算
当需要执行耗时任务(如计算、网络请求)时,使用 std::async
将任务放到后台,主线程可以继续其他工作。
std::future<double> fut = std::async(std::launch::async, [] {
// 模拟复杂计算
return 3.14 * 2;
});
double result = fut.get();
(2)并行任务
运行多个独立任务并收集结果:
std::future<int> fut1 = std::async(std::launch::async, slow_add, 1, 2);
std::future<int> fut2 = std::async(std::launch::async, slow_add, 3, 4);
int sum = fut1.get() + fut2.get(); // 并行计算,结果为 10
(3)异常处理
异步任务中的异常会通过 future
传递:
std::future<int> fut = std::async(std::launch::async, [] {
throw std::runtime_error("错误发生");
return 0;
});
try {
fut.get(); // 抛出异常
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << "\n";
}
5. 与 std::promise
的关系
std::async
是更高层次的抽象,内部可能使用 std::promise
和线程来实现。相比之下,std::promise
提供了更底层的控制:
std::async
:自动管理线程和结果传递,适合简单场景。std::promise
:手动设置结果或异常,适合需要显式控制的复杂线程通信。
例如,std::async
的效果可以用 std::promise
模拟:
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t([](std::promise<int> p) { p.set_value(42); }, std::move(prom));
int result = fut.get();
t.join();
6. 注意事项
(1)线程管理
- 如果用
std::launch::async
,会创建新线程;线程资源有限,滥用可能导致性能问题。 - 如果
future
未被使用(未调用get
或wait
),析构时会阻塞,直到任务完成(std::launch::async
的情况下)。
(2)延迟执行
默认策略下,任务可能不会立即执行,而是等到 get()
或 wait()
调用时才运行。强制异步需明确指定 std::launch::async
。
(3)异常安全
任务抛出的异常会被 future
保存,调用 get()
时抛出,需妥善处理。
(4)性能开销
创建线程和上下文切换有开销,小任务可能不适合用 std::async
,考虑线程池替代。
7. 总结
std::async
:- 用于启动异步任务,提供简单的高级接口。
- 通过策略控制执行方式(异步或延迟)。
std::future
:- 表示异步任务的结果,支持等待、获取值或异常。
- 与
async
、promise
等配合使用。
它们特别适合需要异步执行并获取结果的场景,相比手动管理线程和锁,代码更简洁、安全。如果需要更细粒度的控制,可以结合 std::promise
或线程池使用。