11、C++11 并发编程:std::thread与std::async,开启多线程之旅。
说实话,在C++11之前,写多线程程序是一件挺痛苦的事。你得用POSIX线程或者Windows API,不同平台的代码还不一样。我记得刚入行那会儿,为了在Linux和Windows上跑同一个多线程模块,硬是写了两个版本的代码,维护起来简直是一场噩梦。
C++11终于把并发编程纳入了标准库。这意味着一件事:你可以用同一套代码,跨平台地创建线程、管理异步任务。今天我们就来聊聊两个最核心的工具——std::thread和std::async。
std::thread:最基础的线程控制
std::thread说白了就是直接创建一个操作系统线程。你给它一个可调用对象,它就在新线程里执行。用法非常直观:
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!\n";
}
int main() {
std::thread t(hello);
t.join(); // 等待线程结束
return 0;
}
嗯,这里要注意一个关键点:必须显式调用join()或detach()。如果std::thread对象析构时线程还在运行,程序会直接终止。我曾经在项目里见过一个bug,就是因为某个分支路径忘了调用join(),导致程序莫名其妙崩溃。排查了半天才发现是线程对象销毁时抛了异常。
std::thread 对象在可联结(joinable)状态下析构。要么 join(),要么 detach(),没有第三条路。
给线程传参
线程函数可以带参数,直接跟在可调用对象后面就行:
void print_sum(int a, int b) {
std::cout << a + b << "\n";
}
int main() {
std::thread t(print_sum, 3, 4);
t.join();
return 0;
}
这里有个坑:参数默认是按值传递的。如果你想传引用,必须用std::ref包装一下。我见过有人直接传引用,结果编译不过,一脸懵地跑来问我。其实标准库这么做是为了安全——避免悬空引用。
void modify(int& x) {
x = 42;
}
int main() {
int val = 0;
std::thread t(modify, std::ref(val));
t.join();
// val 现在是 42
return 0;
}
std::async:更高级的异步任务
我个人习惯,如果只是想让某个函数在后台跑一下,然后拿个结果回来,我更倾向于用std::async。它返回一个std::future对象,你可以通过它获取异步操作的结果。
#include <iostream>
#include <future>
int compute(int n) {
return n * n;
}
int main() {
std::future<int> result = std::async(compute, 10);
std::cout << "Result: " << result.get() << "\n";
return 0;
}
你想想看,这比手动管理线程方便多了。不用操心join(),不用处理线程生命周期,std::async全帮你搞定了。
启动策略:懒执行还是立即执行?
std::async可以接受一个启动策略参数。默认情况下,实现可以自由选择是立即启动一个新线程,还是延迟到调用get()时才执行。如果你想明确控制,可以用这两个标志:
| 策略 | 含义 |
|---|---|
std::launch::async |
强制在新线程上立即执行 |
std::launch::deferred |
延迟执行,直到调用 get() 或 wait() |
// 强制异步执行
auto f1 = std::async(std::launch::async, compute, 5);
// 延迟执行
auto f2 = std::async(std::launch::deferred, compute, 6);
int val = f2.get(); // 此时才真正执行
std::thread vs std::async:怎么选?
这个问题我经常被问到。我的建议很简单:
- 需要精细控制线程生命周期(比如设置线程优先级、绑定CPU核心)→ 用
std::thread - 只是想在后台跑个任务,拿个结果 → 用
std::async - 需要线程池或大量短任务 → 两个都不太合适,考虑
std::thread_pool(C++20 之后有标准支持)
说白了,std::async是更高层的抽象,隐藏了线程管理的细节。而std::thread给了你更多的控制权,但也意味着你要承担更多的责任。
避坑指南:我曾经踩过的坑
我曾经在项目里用std::async做并行计算,结果发现性能反而比单线程还差。排查了半天,原来是默认启动策略导致任务被延迟执行了,根本没有并行。后来我显式指定了std::launch::async,问题就解决了。
还有一个常见的坑:std::future的析构函数在某些情况下会阻塞等待结果。如果你创建了多个std::future对象,注意它们的析构顺序,避免意外阻塞。
std::thread直接创建线程,需要手动管理生命周期std::async返回std::future,自动管理线程- 默认启动策略可能延迟执行,性能敏感时显式指定
std::launch::async - 传引用参数时用
std::ref
知识结构图
下面这张图帮你理清今天讲的核心内容:
好了,今天的内容就到这里。多线程编程是个大话题,但std::thread和std::async是你必须掌握的基础工具。下次遇到需要并行处理的任务,先想想用哪个更合适。