71 / 80 · C++20 · 约 10 分钟
线程生命周期:thread、jthread 与 stop_token
线程对象管理执行资源,不等于线程正在运行。先确定谁负责等待结束,再确定共享对象活多久;C++20 的 jthread 自动请求停止并等待,但停止仍需要工作函数合作,不能强制终止阻塞操作。
先分清执行线程与管理对象
构造 std::thread 后,工作函数就可以被调度,不必等调用 join()。线程对象只能移动,不能复制;移动转交管理责任。joinable() 表示仍关联一个需要处理的线程,即使工作函数已经返回,它也可能为真。成功 join() 后才不再可连接。
析构仍可连接的 std::thread 会调用 std::terminate,因此所有正常和异常路径都必须处理它。detach() 只是放弃等待能力,不会延长引用捕获对象的生命。后台线程继续使用已经离开作用域的局部变量,会造成悬垂访问,不能用分离线程修补生命周期设计。
用 jthread 表达作用域所有权
C++20 的 std::jthread 在析构时,若仍可连接,会先请求停止再等待结束。对有明确有限工作量的任务,即使函数不接收停止令牌,自动等待仍然有价值。示例第一段计算固定的一百个整数;离开内部作用域之后,主线程才读取结果。
线程完成与对应 join() 成功返回之间存在同步关系,因此工作线程对 sum 的写入先行发生于随后读取,不需要把只在交接前后访问的结果改成原子变量。被引用的数据必须先于线程对象创建,且晚于线程完成销毁。工作函数若让异常逃出最外层,仍会终止程序;需要在内部捕获并用约定的结果通道交付。
停止是协议,不是强制杀线程
若可调用对象接受首参数 std::stop_token,jthread 会传入自己的停止令牌。request_stop() 记录请求,工作函数通过检查令牌或使用支持停止的等待操作响应。请求成功不代表线程已经结束,释放其使用的资源之前仍要等待。
示例第二段使用 condition_variable_any 的令牌重载,谓词始终为假,所以只有停止请求能让等待正常返回。无论请求发生在等待之前还是期间都能处理,不依赖睡眠或调度先后。普通条件变量等待和任意阻塞 I/O 不会自动响应令牌。标准没有承诺固定毫秒数内完成 join();工程上应保证任务有限,且每个阻塞点都有退出路径。
容易答错的地方
- 不要把 joinable() 当成“还在计算”的查询;工作函数已返回但尚未 join 的线程仍然可连接。
- 不要持有工作线程退出所需的互斥锁再销毁 jthread:析构会等待,而工作线程可能正在等这把锁。
运行一个例子
最低标准 C++20 · 完整程序 · 下载 .cpp
#include <cassert>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <stop_token>
#include <thread>
int main() {
int sum = 0;
{
std::jthread worker([&] {
for (int i = 1; i <= 100; ++i) sum += i;
});
}
assert(sum == 5050);
std::mutex mutex;
std::condition_variable_any changed;
bool cancelled = false;
std::jthread waiter([&](std::stop_token token) {
std::unique_lock<std::mutex> lock(mutex);
const bool ready = changed.wait(lock, token, [] { return false; });
cancelled = !ready && token.stop_requested();
});
waiter.request_stop();
waiter.join();
assert(cancelled);
assert(!waiter.joinable());
std::cout << sum << ' ' << cancelled << '\n';
}
在本地编译
g++ -std=c++20 -Wall -Wextra -Wpedantic -pthread concurrency-threads.cpp -o example && ./example预期结果
5050 1
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
若把第二段换成普通 condition_variable 的无谓词 wait,仅保留 request_stop(),为什么不能保证结束?应怎样修改?
查看参考答案
普通 wait 不订阅停止状态,request_stop 不会自动唤醒它。可保留示例的 condition_variable_any 令牌重载;另一种完整协议是用同一互斥锁保护 done,主线程置 done=true 后 notify_all,工作线程 wait(lock, [&]{ return done; }),最后 join。仅额外 notify 而不保存退出状态,仍可能丢失通知。
继续查证
- cppreference:std::jthread 的析构与停止支持
- cppreference:condition_variable_any::wait 的 stop_token 重载
- cppreference:thread::join 的同步保证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。