std::stop_token 和 std::jthread
一直以来,我们在使用 std::thread
时,需要手动管理线程的 join()
或 detach()
,并且不能直接支持外部请求的中止,这带来了额外的性能开销和编程复杂性。根据 C++ 长期以来的零开销设计哲学,C++20 引入了 std::jthread
和 std::stop_token
。这些新特性不仅自动处理线程的加入,还支持协作式的线程取消,极大简化了线程的使用和管理,而不增加任何未使用功能的成本。 让我们深入了解一下它们的使用和设计哲学。
std::jthread 和 std::stop_token
std::jthread
std::jthread
是 C++20 引入的一个新的线程类,它与 std::thread
类似,但提供了一些重要的改进:
- 自动管理生命周期 :
std::jthread
在作用域结束时会自动调用join
,因此不需要显式地调用join
或detach
。 - 内置停止机制 :
std::jthread
与std::stop_token
集成,支持直接请求停止线程。
- 成员类型
id
:std::thread::id
native_handle_type
(可选):std::thread::native_handle_type
- 成员函数
- 构造函数: 构造新的jthread对象
- 析构函数: 如果线程是可加入的,请求停止并加入线程
- operator=: 移动jthread对象
- joinable: 检查线程是否可加入,即可能在并行上下文中运行
- get_id: 返回线程的id
- native_handle: 返回底层的实现定义线程句柄
- hardware_concurrency [静态]: 返回实现支持的并发线程数
- 操作
- join: 等待线程完成执行
- detach: 允许线程独立于线程句柄执行
- swap: 交换两个jthread对象
- 停止令牌处理
- get_stop_source: 返回与线程的共享停止状态关联的stop_source对象
- get_stop_token: 返回与线程的共享停止状态关联的stop_token
- request_stop: 通过共享停止状态请求执行停止
我们可以看到
std::jthread
支持所有std::thread
的功能并提供了扩展。在自动处理线程生命周期的同时,仍提供join
和detach
方法,保证使用的灵活性和控制。这样设计既维持了与std::thread
的接口一致性,方便开发者使用和过渡。
std::stop_token
下是一个关于 std::stop_token
及其相关类的功能和用途的表格:
类名 | 说明 |
---|---|
std::stop_source |
用于发起停止请求的对象。它负责生成 std::stop_token ,并在需要时发出停止信号。 |
std::stop_token |
由 std::stop_source 创建的令牌,用于在线程中检测是否有停止请求。可以传递给线程,使其能定期检查并决定是否停止执行。 |
std::stop_callback |
允许注册回调函数,这些函数在 std::stop_source 发出停止请求时被调用,提供一种响应停止请求的机制。 |
共同工作机制
- 创建并发起取消请求 :
std::stop_source
对象允许发出取消信号。它可以独立于任何线程存在,并在创建时不自动发起取消请求。 - 生成停止令牌 :
std::stop_source
对象可以生成std::stop_token
,这些令牌可以被多个接收者共享和检查,以确定是否已经请求停止执行。 - 传递停止令牌 :将
std::stop_token
传递给需要支持取消操作的线程或任务。在 C++20 中,std::jthread
自动管理线程的生命周期,并接收一个std::stop_token
作为其运行函数的参数,这简化了令牌的传递和管理。 - 定期检查停止请求 :在长时间运行或循环任务中,应定期调用
std::stop_token::stop_requested()
来检查是否有停止请求。如果返回true
,则表示应当尽快安全地停止执行。 - 响应取消请求 :线程或任务应根据
std::stop_token
的检查结果决定是否退出。这通常涉及清理资源、保存状态等安全退出的步骤。
示例代码
c++
#include <iostream>
#include <chrono>
#include <thread>
#include <stop_token>
// 使用 std::jthread 运行的函数
void task(std::stop_token stoken) {
// 4. 定期检查停止请求
while (!stoken.stop_requested()) {
std::cout << "任务正在运行..." << std::endl;
// 模拟一些工作
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// 5. 响应取消请求
std::cout << "任务已收到停止请求,现在停止运行。" << std::endl;
}
int main() {
// 1. 创建并发起取消请求的源
std::stop_source stopSource;
// 2. 生成停止令牌
std::stop_token stoken = stopSource.get_token();
// 3. 传递停止令牌
std::jthread worker(task, stoken);
// 模拟主线程运行一段时间后需要停止子线程
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "主线程请求停止子线程..." << std::endl;
// 触发停止请求
stopSource.request_stop();
// std::jthread 在析构时自动加入
return 0;
}
底层实现机制
std::jthread
在底层实现中利用了 std::thread
的功能,但添加了对线程取消的支持。std::jthread
的主要特点是它在析构时会自动执行线程的加入(join),并且支持协作式取消。这意味着,如果线程函数接受 std::stop_token
作为参数,std::jthread
会自动从其内部的 std::stop_source
获取一个 std::stop_token
并传递给线程函数,从而使得线程可以在适当的时候响应取消请求。 std::stop_source
和 std::stop_token
提供了一种管理和查询停止状态的机制。std::stop_source
负责发起停止请求,而 std::stop_token
允许在关联的 std::stop_source
发起停止请求后进行查询。当 std::stop_source
调用 request_stop()
时,它会修改内部状态以表示停止已被请求,并通过与之关联的所有 std::stop_token
反映这一状态变更。 这种设计使得 std::jthread
和其协作的取消机制不仅简化了线程的使用,还增强了线程间的协作和通信能力,提高了代码的安全性和可维护性。
std::stop_token
和 std::stop_callback
的其他使用案例;
另外, std::stop_token
和 std::stop_callback
并不局限于与线程(如 std::jthread
)的使用,它们独立于线程的,用于程序中的任何地方,以提供一种灵活的停止信号处理机制。
c++
#include <iostream>
#include <chrono>
#include <stop_token>
int main() {
std::stop_source source;
std::stop_token token = source.get_token();
// 模拟一些可以被取消的工作
auto startTime = std::chrono::steady_clock::now();
auto endTime = startTime + std::chrono::seconds(10); // 设定10秒后结束任务
while (std::chrono::steady_clock::now() < endTime) {
if (token.stop_requested()) {
std::cout << "Task was canceled!" << std::endl;
break;
}
std::cout << "Working..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
// 模拟在某个条件下请求停止
if (std::chrono::steady_clock::now() > startTime + std::chrono::seconds(5)) {
source.request_stop();
}
}
if (!token.stop_requested()) {
std::cout << "Task completed normally." << std::endl;
}
return 0;
}
在这个示例中,我们在主线程中运行一个循环,并通过 std::stop_source
来控制何时应该停止循环。这种方式非常适合于不需要多线程但需要响应取消请求的场景。
它们也可以与 std::thread
结合使用,但使用方式略有不同,因为 std::thread
没有内建的支持来直接接受 std::stop_token
。下面是如何在 std::thread
中使用
c++
#include <iostream>
#include <thread>
#include <stop_token>
void threadFunction(std::stop_token stopToken) {
std::stop_callback cb(stopToken, [](){
std::cout << "Thread stopping as requested." << std::endl;
});
while (!stopToken.stop_requested()) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread is running..." << std::endl;
}
}
int main() {
std::jthread t(threadFunction);
std::this_thread::sleep_for(std::chrono::seconds(5));
t.request_stop(); // Request to stop the thread.
t.join();
return 0;
}
在这个示例中,std::jthread
在构造时自动接收一个 std::stop_token
,该令牌被传递到 threadFunction
。在 threadFunction
中,我们注册了一个 std::stop_callback
,当外部请求停止线程时,这个回调会被触发,输出一条消息并检查 stop_requested()
来优雅地停止线程。
如果你确实需要在 std::thread
中使用 std::stop_token
,你可能需要手动管理这种机制,比如通过线程之间共享的 std::atomic<bool>
或通过其他同步手段来传递停止信号。但通常来说,使用 std::jthread
更为方便和安全。
总结
总的来说,std::jthread
解决 std::thread
缺乏自动资源管理(RAII)的问题,它自动管理线程的生命周期,避免了资源泄漏和死锁的风险,并通过 std::stop_token
增加了能够主动取消或停止线程执行的新特性。