线程管理
线程管理
std::thread 表示一个执行线程的所有权对象。创建线程以后,必须在其析构前明确 join() 或 detach();否则程序调用 std::terminate()。C++20 的 std::jthread 通过析构自动请求停止并 join,通常更适合有作用域的工作线程。
创建与完成线程
1 |
|
join() 只能对可连接线程调用一次。它不仅等待线程结束,也让被连接线程结束前的操作在 join() 返回后对当前线程可见。
| 操作 | 前提 | 效果 |
|---|---|---|
joinable() |
无 | 是否关联一个尚未 join/detach 的线程 |
join() |
joinable() == true |
等待并回收线程执行实体 |
detach() |
joinable() == true |
放弃控制权,线程独立运行 |
析构可连接的 std::thread |
错误 | 调用 std::terminate() |
参数、引用与对象生命周期
构造 std::thread 时,参数会按类似按值保存的方式衰减复制;要传递左值引用需用 std::ref。线程可能在创建后立即运行,因此被引用或捕获的对象必须活到线程不再访问它为止。
1 |
|
上例的 join() 同时保证 value 的生命周期覆盖线程执行。若多个线程读写 value,还需要互斥锁或原子对象;std::ref 本身不提供同步。
1 | // 危险:函数返回后 local 已销毁,分离线程中的引用悬空。 |
lambda 与移动所有权
lambda 推荐显式捕获。值捕获适合让任务拥有一份副本;引用捕获需要额外证明生命周期和同步正确。不可复制资源可移动进 lambda:
1 |
|
detach():何时不应使用
detach() 不会让线程“自动安全”:它只解除 std::thread 对象与执行线程的关联。进程退出时,仍在运行的线程会被操作系统终止;也无法再 join、传播异常或可靠地等待其完成。
除非后台任务访问的资源具有明确的进程级生命周期,并且退出协议已经设计好,否则不要使用 detach()。大多数场景可用 join、std::jthread、任务队列或由长期存活对象统一管理线程替代。
C++20:std::jthread 与协作式停止
std::jthread 在析构时先请求停止,再 join()。停止请求是协作式的:线程函数必须检查 std::stop_token;它不会强制杀死线程。
1 |
|
std::jthread 需要 C++20。C++17 中可使用 std::thread 加 std::atomic<bool> 停止标志,并由所有者设置标志后 join();条件变量等待还应在停止时 notify_all()。
调度辅助 API
| API | 返回或作用 | 注意 |
|---|---|---|
std::this_thread::get_id() |
当前线程 ID | 主要用于日志,不应用作业务身份 |
std::thread::hardware_concurrency() |
并发硬件线程数提示,可能为 0 | 不是线程池大小的可靠配置 |
sleep_for(duration) |
至少等待指定时长后才可继续运行 | 不保证精确定时,不应用于同步 |
yield() |
提示调度器让出当前时间片 | 不保证其他线程运行,不能修复竞态 |
异常与 RAII
线程函数中的未捕获异常会调用 std::terminate(),不会自动传回创建者。需要传递结果或异常时,使用 std::promise/std::future,见异步机制。
在 C++17 中,最简单可靠的 RAII 方案是让拥有线程的对象在析构前执行已设计好的停止协议,再 join()。不要把“析构时无条件 join”当成通用方案:如果工作线程需要调用方先发出停止信号,它可能永久阻塞。
常见误区与实践建议
join()不会并行运行任务;它只是等待已启动的线程结束。- 线程数不是越多越好。先测量负载与阻塞比例,并为任务队列设置上限。
- 不要以
sleep_for代替“等待条件成立”;请用条件变量、future 或其他同步原语。 - 线程对象可移动、不可复制;移动后旧对象通常不再
joinable()。 - 编译示例:
c++ -std=c++17 -Wall -Wextra -Wpedantic -pthread file.cpp;std::jthread示例改用-std=c++20。



