在C++11之前,多线程编程往往意味着直接操作线程原语——创建线程、管理线程生命周期、处理线程同步,这些底层细节让开发者不得不分心于机械性工作而非业务逻辑。std::async的出现改变了这一局面,它就像一位得力的助手,帮你把"怎么执行"的琐事包揽下来,让你专注思考"执行什么"。
想象你是一位餐厅经理(主线程),当顾客(任务)到来时,你可以选择:
std::async提供的正是后两种方案的优雅实现。其核心优势在于:
cpp复制// 简化后的函数签名(C++17起)
template<class F, class... Args>
std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>>
async(F&& f, Args&&... args);
// 带执行策略的版本
template<class F, class... Args>
std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>>
async(std::launch policy, F&& f, Args&&... args);
关键点解析:
F可以是任何可调用对象:函数指针、lambda、函数对象等Args...是完美转发的参数包std::invoke_result_t推导)cpp复制auto calculate_pi = [](int iterations) {
double sum = 0.0;
for (int i = 0; i < iterations; ++i) {
double term = 1.0 / (2 * i + 1);
sum += (i % 2 == 0) ? term : -term;
}
return 4.0 * sum;
};
auto fut = std::async(std::launch::async, calculate_pi, 1'000'000);
// 主线程可以继续其他工作...
std::cout << "PI ≈ " << fut.get() << std::endl;
cpp复制auto fetch_data = [](const std::string& url) {
// 模拟网络请求
std::this_thread::sleep_for(1s);
return "Data from " + url;
};
auto fut = std::async(fetch_data, "https://api.example.com/data");
// 在等待时可以处理其他事情
if(fut.wait_for(500ms) == std::future_status::ready) {
std::cout << fut.get() << std::endl;
}
| 策略类型 | 执行时机 | 执行线程 | 适用场景 | 注意事项 |
|---|---|---|---|---|
| async | 立即异步 | 新线程/线程池 | 计算密集型任务 | 可能立即消耗系统资源 |
| deferred | 调用get/wait时 | 调用者线程 | 惰性求值 | 可能导致意外阻塞 |
| 默认策略 | 实现定义 | 实现定义 | 一般用途 | 行为可能随平台变化 |
重要提示:MSVC的实现中,默认策略倾向于async,而GCC可能更保守。这种差异可能导致跨平台应用行为不一致。
当异步任务抛出异常时,异常会被捕获并存储在future中,直到调用get()时重新抛出:
cpp复制auto risky_task = []() {
throw std::runtime_error("Something went wrong!");
return 42;
};
auto fut = std::async(std::launch::async, risky_task);
try {
auto result = fut.get(); // 这里会抛出异常
} catch(const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
常见坑点:参数的生命周期必须保证在任务执行期间有效
cpp复制// 危险示例!
std::string config = load_config();
auto fut = std::async(std::launch::async, [&config]() {
// config可能已被销毁!
return process(config);
});
config.clear(); // 可能导致竞态条件
// 正确做法:传值或使用shared_ptr
auto fut = std::async(std::launch::async, [config]() {
return process(config);
});
cpp复制std::vector<std::future<int>> futures;
for (int i = 0; i < 10; ++i) {
futures.emplace_back(std::async(std::launch::async,
[i]() { return i * i; }
));
}
// 使用wait_for_all辅助函数
auto results = wait_for_all(futures);
cpp复制std::packaged_task<int()> task([](){ return 7 * 6; });
auto fut = task.get_future();
std::async(std::launch::async, std::move(task));
std::cout << "Result: " << fut.get() << std::endl;
cpp复制std::promise<std::string> prom;
auto fut = prom.get_future();
std::async(std::launch::async, [&prom]() {
try {
prom.set_value(fetch_data());
} catch(...) {
prom.set_exception(std::current_exception());
}
});
在我的基准测试中(i7-11800H, Windows 11),创建1000个简单任务:
结论:对于短任务,async比裸线程高效,但不如专用线程池。
通过Valgrind检测发现:
症状:程序在调用get()时无限等待
可能原因:
诊断步骤:
fut.wait_for(0s)ulimit -u)现象:虽然按顺序创建async任务,但执行顺序不确定
解决方案:
std::async(std::launch::deferred)确保顺序执行fut1.get()后再启动fut2std::experimental::when_allC++17/20对async的改进:
std::future增加then()方法(未进入标准)std::async的并行算法版本个人建议:对于新项目,可以考虑:
std::for_each+std::execution::par)