C++ 标准库 :让异步编程不再难懂
在现代 C++ 编程中,多线程与异步操作已经不再是“高级技巧”,而是构建高性能、高响应性程序的必备能力。而 C++ 标准库提供的
如果你曾经为线程间通信、任务结果获取、超时控制等问题头疼,那么 C++ 标准库
本文将带你从零开始,逐步掌握
什么是 future 和 promise?
想象你去餐厅点了一份牛排。厨师开始烹饪,但你不能一直等在厨房门口。于是你拿到一个“取餐号”,这个号就是你未来能拿到牛排的凭证——这正是 future 的角色。
而 promise,就是那个厨师在完成牛排后,用来“交付”结果的机制。你无法直接把牛排塞给顾客,必须通过 promise 设置结果,然后 future 才能“取出”它。
在 C++ 中:
std::future<T>表示一个异步操作的结果,你可以通过.get()获取结果。std::promise<T>用于设置未来结果,通常由另一个线程调用.set_value()来赋值。
#include <iostream>
#include <future>
#include <thread>
#include <string>
// 一个简单的异步任务:计算斐波那契数列第 n 项
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
// 创建一个 promise 和对应的 future
std::promise<int> my_promise;
std::future<int> my_future = my_promise.get_future(); // 获取 future
// 在另一个线程中执行任务,并设置结果
std::thread worker([&my_promise]() {
int result = fibonacci(10); // 计算斐波那契数列第10项
my_promise.set_value(result); // 通过 promise 设置结果
});
// 主线程可以去做其他事情,比如打印提示
std::cout << "正在等待异步任务完成..." << std::endl;
// 等待 future 结果,阻塞直到结果可用
int answer = my_future.get();
std::cout << "异步任务完成,结果是: " << answer << std::endl;
worker.join(); // 等待子线程结束
return 0;
}
注释说明:
my_promise.get_future()从 promise 中提取 future,用于后续获取结果。std::thread worker(...)启动新线程执行斐波那契计算。my_promise.set_value(result)是设置结果的关键调用,必须在某个线程中执行。my_future.get()是阻塞调用,直到 promise 设置了值才会返回。worker.join()确保子线程结束,避免资源泄漏。
使用 async 简化异步任务
虽然 promise 和 future 非常灵活,但每次都要手动创建线程和管理生命周期,略显繁琐。C++ 提供了 std::async 函数,可以自动帮你处理线程创建和任务调度,大大简化了异步编程。
std::async 会自动将函数包装成一个异步任务,并返回一个 std::future 对象。你可以直接调用 .get() 获取结果,就像在等待一个“未来事件”。
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
// 模拟一个耗时的网络请求
int fetch_data_from_server() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟 2 秒延迟
return 42; // 模拟返回数据
}
int main() {
std::cout << "发起异步请求..." << std::endl;
// 使用 std::async 启动异步任务
std::future<int> result = std::async(std::launch::async, fetch_data_from_server);
std::cout << "主线程继续执行其他任务..." << std::endl;
// 模拟主线程做点别的事
for (int i = 0; i < 5; ++i) {
std::cout << "正在处理第 " << i + 1 << " 个任务..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
// 等待异步结果,阻塞直到完成
int data = result.get();
std::cout << "收到服务器返回数据: " << data << std::endl;
return 0;
}
注释说明:
std::async(std::launch::async, fetch_data_from_server)会创建一个新线程执行函数。std::launch::async表示立即异步执行,也可使用std::launch::deferred延迟执行。result.get()会阻塞直到任务完成,是获取结果的唯一方式。- 主线程可以自由做其他事情,体现了异步的优势。
任务超时与等待策略
在真实系统中,异步任务可能因为网络问题、资源不足等原因无法及时完成。因此,我们不能永远等待。C++ 提供了 wait_for 和 wait_until 方法,用于设置超时等待。
#include <iostream>
#include <future>
#include <chrono>
// 模拟一个可能超时的任务
int long_running_task() {
std::this_thread::sleep_for(std::chrono::seconds(5));
return 100;
}
int main() {
std::future<int> task = std::async(std::launch::async, long_running_task);
std::cout << "等待任务完成,最多等待 3 秒..." << std::endl;
// 等待最多 3 秒,超时则返回
auto status = task.wait_for(std::chrono::seconds(3));
if (status == std::future_status::ready) {
std::cout << "任务已完成,结果是: " << task.get() << std::endl;
} else if (status == std::future_status::timeout) {
std::cout << "任务超时,未完成。" << std::endl;
} else {
std::cout << "任务未就绪(可能是被取消)" << std::endl;
}
return 0;
}
注释说明:
task.wait_for(std::chrono::seconds(3))返回std::future_status枚举值。std::future_status::ready表示任务已完成。std::future_status::timeout表示等待超时。- 通过判断状态,可以避免无限阻塞,提升程序健壮性。
多个 future 的管理:当任务不止一个
在实际项目中,我们常常需要并发执行多个异步任务。C++ 提供了 std::future::wait_for 和 std::future::wait_until,以及更高级的 std::when_all 和 std::when_any(虽然这些在 C++17 以后才被引入),但我们可以用 std::future 的组合方式来管理多个任务。
#include <iostream>
#include <vector>
#include <future>
#include <thread>
#include <chrono>
int compute_square(int x) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return x * x;
}
int main() {
std::vector<std::future<int>> futures;
// 启动多个异步任务
for (int i = 1; i <= 5; ++i) {
futures.push_back(std::async(std::launch::async, compute_square, i));
}
std::cout << "所有任务已启动,正在等待结果..." << std::endl;
// 逐一获取结果
for (auto& f : futures) {
int result = f.get(); // 阻塞等待该任务完成
std::cout << "计算结果: " << result << std::endl;
}
std::cout << "所有任务完成。" << std::endl;
return 0;
}
注释说明:
- 使用
std::vector<std::future<int>>存储多个 future。- 每个
f.get()都会阻塞,直到对应任务完成。- 适用于任务数量固定、需要逐个处理结果的场景。
常见陷阱与最佳实践
在使用 C++ 标准库
| 陷阱 | 说明 | 如何避免 |
|---|---|---|
重复调用 .get() |
一旦调用 .get(),结果就被取出,再次调用会抛出异常 |
确保只调用一次 |
忘记 join() 或 detach() |
子线程未结束可能导致资源泄漏 | 使用 worker.join() 确保线程结束 |
在 promise 未设置值前调用 get() |
会导致 std::future_error 异常 |
确保 promise 在 future 调用 get 之前设置值 |
使用 std::launch::deferred 但未调用 get() |
任务不会执行 | 显式调用 .get() 触发执行 |
此外,建议在使用 std::async 时,优先考虑 std::launch::async,除非你明确需要延迟执行。对于复杂任务,推荐使用线程池 + std::packaged_task 进行更精细的控制。
总结:掌握未来,掌控并发
C++ 标准库 future 和 promise 的协作机制,让你能够清晰地表达“我需要一个结果,但不想阻塞当前线程”的意图。
从简单的 async 调用,到复杂的多任务管理,再到超时控制与状态检测,
无论你是初学者还是中级开发者,理解并熟练使用 C++ 标准库
下一次当你写一个耗时操作时,不妨试试用 std::async 封装它,让主线程自由呼吸,让程序真正“异步”起来。