C++ 多线程库 thread
下面对 C++11 及以后标准中 <thread>
头文件所提供的多线程支持做一次系统、深入的梳理,包括线程管理类、线程局部存储、线程休眠与让渡、硬件并发查询,以及实践建议与注意事项。
一、概述
<thread>
是 C++ 标准库提供的跨平台线程支持,封装了操作系统原生线程接口。- 核心类型与函数分布在三个命名空间:
std::thread
:线程类std::this_thread
:当前线程相关操作std::hardware_concurrency
:并发度查询
二、std::thread
类
1. 定义与构造
namespace std {
class thread {
public:
// 构造
thread() noexcept; // 不代表任何线程
template<class F, class... Args>
explicit thread(F&& f, Args&&... args);
// 启动新线程,调用 std::invoke(f, args...)
…
};
}
- 无参构造:创建“空”线程对象,不代表任何执行单元。
- 函数对象构造:将可调用对象(函数指针、lambda、函数对象)和其参数拷贝/移动到新线程中执行。
2. 成员函数
函数 | 功能 |
---|---|
join() | 阻塞当前线程直到该 std::thread 完成;必须可 join,否则 std::terminate 。 |
detach() | 分离线程——线程完成后自主释放资源;调用后不可再 join() 或 detach() 。 |
joinable() | 判断是否为可 join 状态(即已关联且未 join/detach)。 |
get_id() | 返回 std::thread::id ,可用于标识或比较线程。 |
native_handle() | 获取底层原生线程句柄,可用于平台特定调优或 API 调用。 |
#include <thread>
#include <iostream>
void worker(int id) {
std::cout << "Worker " << id << " start\n";
// …
std::cout << "Worker " << id << " end\n";
}
int main() {
std::thread t1(worker, 1);
if (t1.joinable()) t1.join();
}
- 注意:若一个
std::thread
对象在析构时仍为 joinable,会调用std::terminate
——务必在析构前join()
或detach()
。
三、std::this_thread
命名空间
提供当前线程的工具函数。
函数 | 功能 |
---|---|
this_thread::get_id() | 返回当前线程的 std::thread::id 。 |
this_thread::yield() | 暂让当前线程执行权,使调度器有机会切换到其他就绪线程。 |
this_thread::sleep_for(d) | 阻塞当前线程持续指定 std::chrono::duration 。 |
this_thread::sleep_until(tp) | 阻塞当前线程直到指定 std::chrono::time_point 。 |
#include <thread>
#include <chrono>
std::this_thread::sleep_for(std::chrono::milliseconds(100));
yield()
常用于自旋锁或忙等循环中,让出 CPU。- 睡眠 精度受操作系统调度影响,不保证严格时长。
四、线程局部存储(thread_local
)
- C++11 引入了
thread_local
存储修饰符,用于声明每个线程独有的一份变量副本。 - 对比:
static
是进程内单例,thread_local
是每个线程单例。
thread_local int counter = 0;
void func() {
++counter;
// 每个线程都有自己独立的 counter
}
- 应用场景:避免对共享数据加锁,存放线程私有的缓存、状态或错误码等。
五、并发度查询
unsigned n = std::thread::hardware_concurrency();
- 返回系统建议的并行线程数量,通常等于逻辑 CPU 核心数。
- 若实现不支持或无法检测,可能返回 0。
六、实践建议与注意事项
- RAII 管理线程
- 推荐封装一个小型的线程守护类,在析构时自动
join()
或detach()
,避免忘记管理:struct ThreadGuard { std::thread t; ~ThreadGuard() { if (t.joinable()) t.join(); } };
- 推荐封装一个小型的线程守护类,在析构时自动
- 避免数据竞争
- 对多个线程访问同一可变数据时,使用互斥量(
<mutex>
)、读写锁或原子操作(<atomic>
)保护。
- 对多个线程访问同一可变数据时,使用互斥量(
- 慎用
detach()
detach()
后你无法知道线程何时完成,也无法获取其返回值或异常——仅在后台运行独立任务、且不关心其结果时使用。
- 线程参数传递
std::thread
构造时会复制或移动参数;如需传引用,必须使用std::ref()
:int x = 0; std::thread t([](int& v){ v = 1; }, std::ref(x));
- 控制并发度
- 根据
hardware_concurrency
限制同时运行线程数量,避免过度切换开销。 - 可用线程池复用线程(标准库尚未提供,建议使用第三方或自行实现)。
- 根据
- 异常安全
- 线程函数若抛异常,若未捕获会导致
std::terminate
,可在函数内捕捉所有异常并传回主线程或记录日志。
- 线程函数若抛异常,若未捕获会导致
- 线程亲和性(Affinity)
- 如需绑定线程到特定 CPU,需调用
native_handle()
并使用平台 API(如 pthread 或 WinAPI)设置。
- 如需绑定线程到特定 CPU,需调用
- 使用高层并行框架
- 对于大量并行任务,优先考虑
<future>
/<async>
、并行算法(<algorithm>
+<execution>
)、或第三方线程池,避免直接管理原始线程过多细节。
- 对于大量并行任务,优先考虑
通过以上对 <thread>
的详尽讲解与实践建议,你可以更安全、高效地在现代 C++ 中组织与管理多线程任务。祝编码顺利!