引言
随着多核处理器的普及,C++程序员越来越需要利用并发编程来提升程序性能和响应能力。C++11标准引入了丰富的并发支持,其中std::async和std::future是最常用的异步任务和结果获取机制。
一、std::future与std::promise
1.1 什么是std::future?
std::future是C++11标准库提供的一个模板类,用于保存异步任务的结果。它可以看作是“未来某个时间可用的值”的占位符。通过std::future对象,主线程可以等待或获取异步任务的返回值。
1.2 什么是std::promise?
std::promise则是用于在一个线程中设置值,并让另一个线程通过std::future获取该值的机制。它们通常成对出现,适用于自定义线程间通信。
1.3 std::async的作用
std::async是C++11标准库中用于启动异步任务的函数模板。它自动将一个可调用对象(函数、lambda、成员函数等)放到新的线程或延迟执行,并返回一个std::future对象,用于获取结果。
二、std::async的用法详解
2.1 基本用法
#include <iostream>
#include <future>
// 计算阶乘的函数
int Factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
int main() {
// 启动异步任务,计算5的阶乘
std::future<int> fut = std::async(Factorial, 5);
// 在这里可以做其他事情...
// 获取异步任务的结果(阻塞直到完成)
int result = fut.get();
std::cout << "5! = " << result << std::endl;
return 0;
}
说明
std::async会立即启动一个异步任务,并返回一个std::future对象。调用
future.get()会阻塞当前线程,直到异步任务完成并返回结果。
2.2 std::launch策略
std::async允许通过std::launch参数指定任务的启动策略:
std::launch::async:强制在新线程中异步执行。std::launch::deferred:延迟执行,只有在调用get()或wait()时才执行,且在调用线程中运行。std::launch::async | std::launch::deferred(默认):由实现决定。
// 强制异步执行
auto fut1 = std::async(std::launch::async, Factorial, 6);
// 强制延迟执行
auto fut2 = std::async(std::launch::deferred, Factorial, 7);
注意事项
使用
deferred策略时,任务不会在后台线程中运行,而是在调用get()或wait()时同步执行。如果不指定策略,编译器和运行库会根据实现自行决定。
2.3 结果获取与异常传递
获取结果
future.get():获取异步任务的返回值,若尚未完成则阻塞。future.wait():阻塞直到任务完成,但不获取结果。future.wait_for()和future.wait_until():带超时等待。
异常传递
如果异步任务抛出异常,调用get()时会重新抛出该异常。
#include <stdexcept>
int ThrowException() {
throw std::runtime_error("Something went wrong!");
}
int main() {
std::future<int> fut = std::async(ThrowException);
try {
int result = fut.get(); // 这里会抛出异常
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
}
三、std::future与std::promise的配合
在自定义线程中,std::promise和std::future经常搭配使用,实现线程间的数据传递。
#include <thread>
#include <future>
#include <iostream>
// 线程函数,通过promise设置结果
void CalculateFactorial(std::promise<int> prom, int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
prom.set_value(result); // 设置结果
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(CalculateFactorial, std::move(prom), 8);
// 这里可以做其他事情...
int result = fut.get();
std::cout << "8! = " << result << std::endl;
t.join();
}
四、适用场景与原理分析
4.1 适用场景
异步计算任务:如网络请求、磁盘IO、复杂计算等,主线程无需等待即可继续执行。
任务结果同步:主线程可通过
future等待或获取子任务结果。异常安全:异步任务中的异常可自动传递到主线程。
4.2 原理简析
std::async底层会根据策略选择是否创建新线程(通常用std::thread实现),或者延迟在调用线程中执行。std::future和std::promise底层通过共享状态(shared state)实现线程间通信。当
future.get()被调用时,会等待共享状态就绪(即任务完成或异常抛出)。
4.3 与其他并发机制的对比
std::thread:需要手动管理线程生命周期,无法直接获取返回值或异常。
std::packaged_task:将可调用对象包装成任务,与
future配合使用,但更底层。std::async:自动管理线程、返回值和异常,适合简单并发场景。
五、扩展知识:std::shared_future与多线程结果共享
如果多个线程需要获取同一个异步结果,可以使用std::shared_future。
#include <future>
#include <iostream>
#include <thread>
void PrintResult(std::shared_future<int> sfut) {
std::cout << "Result: " << sfut.get() << std::endl;
}
int main() {
std::future<int> fut = std::async(Factorial, 9);
std::shared_future<int> sfut = fut.share();
// 所有线程都可以调用sfut.get()
std::thread t1(PrintResult, sfut);
std::thread t2(PrintResult, sfut);
t1.join();
t2.join();
}
六、常见问题与注意事项
future.get()只能调用一次,后续调用会抛出
std::future_error。避免资源泄漏:使用
std::async时,不要忽略返回的future对象,否则任务异常或未完成时会导致程序异常终止。线程数量受限:频繁创建线程可能导致资源耗尽,适合中小规模并发任务。大型并发建议使用线程池。
七、总结
std::async和std::future为C++并发编程提供了强大而安全的异步任务管理机制。合理选择启动策略和同步方式,能显著提升程序性能与结构清晰度。
在实际开发中,应根据任务复杂度和资源情况,灵活选择并发工具。
评论区