在 C++11 之后,多线程编程得到了标准库的直接支持。两种最常用的并发入口是 std::thread 与 std::async,它们各自有不同的语义、生命周期管理和错误处理方式。本文将从两者的基本概念、创建方式、资源管理、异常传播、返回值处理以及适用场景等方面进行对比,帮助你在实际项目中做出更合适的选择。
1. 基本概念与语义
| 特性 | std::thread |
std::async |
|---|---|---|
| 创建方式 | 立即创建并启动新线程 | 延迟(懒加载)或立即执行,取决于 launch 策略 |
| 返回值 | 无(通过 join 或 detach 处理) |
`std::future |
| `,可异步获取结果 | ||
| 异常传播 | 只会在 join 时抛出 |
通过 future::get() 传播 |
| 线程管理 | 需要手动 join 或 detach |
自动在 future 销毁时等待(若未 get()) |
2. 创建方式
std::thread
void worker(int id) {
std::cout << "Worker " << id << " start\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Worker " << id << " finish\n";
}
int main() {
std::thread t(worker, 1);
t.join(); // 等待线程结束
}
- 立即执行:线程一旦构造就立即开始运行。
- 显式生命周期:如果不
join()或detach(),程序会因为std::terminate被调用而崩溃。
std::async
int compute(int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(2));
return a + b;
}
int main() {
auto fut = std::async(std::launch::async, compute, 3, 4);
std::cout << "Result: " << fut.get() << "\n";
}
- 延迟执行:默认
launch::async | launch::deferred,如果没有显式指定,编译器可以决定是否立即执行或在get()时执行。 - 返回值:`std::future ` 包装了结果或异常。
3. 资源管理与异常
-
std::thread:必须确保join()或detach()。join()会等待线程完成,并且如果线程抛出异常,异常会被吞掉(除非在thread里手动捕获)。detach()让线程成为后台线程,结束后资源自动回收,但无法获取返回值。 -
std::async:异常会被捕获并存储在future对象中,直到调用get()或wait()。如果future被销毁而没有get(),线程会被自动join(),确保资源正确释放。
4. 返回值与同步
- 使用
std::thread:若需要线程返回值,需自己实现共享变量或使用std::promise与std::future。
std::promise <int> prom;
std::future <int> fut = prom.get_future();
std::thread t([&prom](){
prom.set_value(42);
});
t.join();
std::cout << "Result: " << fut.get() << "\n";
- 使用
std::async:直接返回future,调用get()就能拿到结果,简洁直观。
5. 性能与调度
-
std::thread:总是使用新的线程。适合需要持续运行的任务或需要与 OS 线程高度耦合的场景(如 UI 线程、网络服务端线程池)。 -
std::async:在launch::deferred模式下,函数会在get()时同步执行,避免不必要的线程创建。适合轻量级、一次性计算任务。
6. 典型使用场景对比
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 需要立即启动后台线程并持续运行 | std::thread |
线程独立,生命周期可控 |
| 一次性并行计算并获取结果 | std::async |
自动管理 future,异常安全 |
| 需要共享复杂结果或异常传递 | std::async |
future::get() 直接传播异常 |
| 多线程之间需要同步与协作 | std::thread + std::mutex/std::condition_variable |
线程间共享内存,控制更细 |
| 需要延迟执行或懒加载 | std::async |
launch::deferred 可避免不必要线程 |
7. 小结
std::thread:更底层、更灵活,但需要手动管理线程生命周期和错误。适用于持续运行或与 OS 线程高度耦合的场景。std::async:更高级的抽象,自动管理结果与异常,适合一次性计算或需要懒加载的情况。
在实际项目中,建议先用 std::async 处理一次性并行任务,遇到需要长期并发或与系统层面交互时再切换到 std::thread。了解两者的细微差别后,你可以根据具体需求选择最合适的并发工具,提高代码的可读性和可维护性。