线程管理

std::thread 表示一个执行线程的所有权对象。创建线程以后,必须在其析构前明确 join() 或 detach();否则程序调用 std::terminate()。C++20 的 std::jthread 通过析构自动请求停止并 join,通常更适合有作用域的工作线程。

创建与完成线程

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>

void print_task() {
std::cout << "子线程正在工作\n";
}

int main() {
std::thread worker(print_task); // 开始执行;实际何时获得 CPU 由调度器决定
worker.join(); // 阻塞当前线程,直到 worker 结束
}

join() 只能对可连接线程调用一次。它不仅等待线程结束,也让被连接线程结束前的操作在 join() 返回后对当前线程可见。

操作 前提 效果
joinable() 无 是否关联一个尚未 join/detach 的线程
join() joinable() == true 等待并回收线程执行实体
detach() joinable() == true 放弃控制权,线程独立运行
析构可连接的 std::thread 错误 调用 std::terminate()

参数、引用与对象生命周期

构造 std::thread 时,参数会按类似按值保存的方式衰减复制;要传递左值引用需用 std::ref。线程可能在创建后立即运行,因此被引用或捕获的对象必须活到线程不再访问它为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <functional>
#include <iostream>
#include <thread>

void add_ten(int& value) {
value += 10;
}

int main() {
int value = 5;
std::thread worker(add_ten, std::ref(value));
worker.join();
std::cout << value << '\n'; // 15
}

上例的 join() 同时保证 value 的生命周期覆盖线程执行。若多个线程读写 value,还需要互斥锁或原子对象;std::ref 本身不提供同步。

1
2
// 危险:函数返回后 local 已销毁,分离线程中的引用悬空。
// std::thread([&local] { use(local); }).detach();

lambda 与移动所有权

lambda 推荐显式捕获。值捕获适合让任务拥有一份副本;引用捕获需要额外证明生命周期和同步正确。不可复制资源可移动进 lambda:

1
2
3
4
5
6
7
8
9
10
#include <memory>
#include <thread>

int main() {
auto value = std::make_unique<int>(42);
std::thread worker([owned = std::move(value)] {
// owned 唯一拥有该对象
});
worker.join();
}

detach():何时不应使用

detach() 不会让线程“自动安全”:它只解除 std::thread 对象与执行线程的关联。进程退出时,仍在运行的线程会被操作系统终止;也无法再 join、传播异常或可靠地等待其完成。

除非后台任务访问的资源具有明确的进程级生命周期,并且退出协议已经设计好,否则不要使用 detach()。大多数场景可用 join、std::jthread、任务队列或由长期存活对象统一管理线程替代。

C++20:std::jthread 与协作式停止

std::jthread 在析构时先请求停止,再 join()。停止请求是协作式的:线程函数必须检查 std::stop_token;它不会强制杀死线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>

int main() {
std::jthread worker([](std::stop_token stop) {
while (!stop.stop_requested()) {
// 做一小段可中断工作;真实程序不要无限忙等
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "已收到停止请求\n";
});

// 作用域结束时自动 request_stop(),然后 join()
}

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。