C++ 中的多线程编程:`std::thread` 与 `std::async` 的区别与使用场景

在 C++11 之后,多线程编程得到了标准库的直接支持。两种最常用的并发入口是 std::threadstd::async,它们各自有不同的语义、生命周期管理和错误处理方式。本文将从两者的基本概念、创建方式、资源管理、异常传播、返回值处理以及适用场景等方面进行对比,帮助你在实际项目中做出更合适的选择。

1. 基本概念与语义

特性 std::thread std::async
创建方式 立即创建并启动新线程 延迟(懒加载)或立即执行,取决于 launch 策略
返回值 无(通过 joindetach 处理) `std::future
`,可异步获取结果
异常传播 只会在 join 时抛出 通过 future::get() 传播
线程管理 需要手动 joindetach 自动在 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::promisestd::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。了解两者的细微差别后,你可以根据具体需求选择最合适的并发工具,提高代码的可读性和可维护性。

发表评论