std::stop_source 是 C++20 引入的标准库组件(位于 <stop_token> 头文件中),用于在多线程编程中实现协同式线程取消(Cooperative Thread Cancellation)。
它提供了一种线程安全、优雅且通用的机制,用来发起"停止请求",使运行中的异步任务或后台线程能够主动检查并安全退出。
背景对比 :在 C++20 之前,终止线程通常依赖手动维护
std::atomic<bool>标志位,或者使用平台特定的 API(如 POSIXpthread_cancel)。然而平台强制终止极易导致 RAII 析构函数未运行、互斥锁死锁以及资源泄漏等未定义行为(UB)。
核心协作三元组
C++20 的协同取消机制由三个相互协作的组件构成:
std::stop_source(信号控制端) :负责发起停止信号(调用.request_stop())。std::stop_token(信号观察端) :传递给工作线程,用于轮询或检查是否收到了停止信号(调用.stop_requested())。std::stop_callback(回调绑定机制) :允许注册回调函数,当停止请求被发起时同步触发,用于打断处于阻塞状态的 I/O 或条件变量。
一、 API 详解
1. std::stop_source 核心接口
核心控制与 Token 获取
-
**
request_stop()->bool** -
作用:发起停止请求。
-
返回值 :首次成功发起停止请求返回
true;若此前已发起过停止请求,或当前对象未关联有效的底层状态,返回false。 -
副作用 :使所有关联的
std::stop_token::stop_requested()返回true,并同步触发 所有已注册的std::stop_callback。 -
**
get_token()->std::stop_token** -
作用 :获取与当前
stop_source绑定共享状态的std::stop_token对象,用于下发给工作线程。
状态查询
-
**
stop_requested() const->bool** -
作用:检查关联的底层状态是否已收到停止请求。
-
**
stop_possible() const->bool** -
作用 :检查当前对象是否仍有可能 发起停止请求。若关联了有效状态且未被全部释放,返回
true;若为默认构造的空对象,或所有关联的stop_source均已销毁,返回false。
生命周期与比较
-
构造函数:
-
stop_source():默认构造,创建一个新的可触发停止请求的底层共享状态。 -
stop_source(std::nostopstate_t):构造一个不带底层状态的空对象(stop_possible()为false)。 -
拷贝 / 移动构造 :支持拷贝与移动。由于底层使用引用计数管理状态,拷贝出的多个
stop_source共享同一个底层状态。 -
**
swap()/std::swap**:交换两个stop_source对象的底层状态。 -
比较运算符 (
==/!=) :判断两个stop_source是否共享同一个底层停止状态(或均为空)。
2. std::stop_token 核心接口
状态查询
-
**
stop_requested() const->bool** -
作用 :检查关联的
std::stop_source是否已发起停止请求。 -
典型用途 :作为工作线程主循环的退出条件(如
while (!stoken.stop_requested()))。 -
**
stop_possible() const->bool** -
作用 :检查当前
token是否仍有可能 收到停止请求。若关联了有效状态且至少存在一个存活的stop_source,返回true;若所有stop_source均已析构,或token本身为无状态对象(nostopstate),返回false。 -
优化场景 :在长循环中,若
stop_possible()为false,可提前终止对stop_requested()的无效轮询。
生命周期与比较
- 构造函数 :
stop_token()创建不包含底层状态的空 token(stop_possible()和stop_requested()均返回false)。 - 拷贝与移动 :支持轻量级拷贝与移动,多个
stop_token可分发至多个并发线程,共享同一个状态观察点。 swap()与比较运算符 (==/!=) :与stop_source行为一致,比较或交换底层共享状态。
3. std::stop_source 与 std::stop_token 对比
| 维度 | std::stop_source |
std::stop_token |
|---|---|---|
| 角色定义 | 信号控制者(控制端) | 信号观察者(只读端) |
| 核心职责 | 发起停止请求(request_stop()) |
检查停止状态(stop_requested()) |
| 线程归属 | 通常由主控线程 / 管理线程持有 | 传递给一个或多个后台工作线程 |
| 接口安全性 | 拥有修改权,可改变底层共享状态 | 完全只读,无法发起取消,天然编译期接口安全 |
| 生命周期依赖 | 只要存在一个存活的 stop_source,即可触发取消 |
即使所有 stop_source 均已析构,stop_token 仍可安全查询(但 stop_possible() 会变为 false) |
二、 代码示例
示例 1:配合 std::thread 或异步任务手动控制
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <stop_token>
void worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::cout << "Worker running...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << "Worker received stop request, cleaning up and exiting.\n";
}
int main() {
std::stop_source source;
// 将与其绑定的 stop_token 传给工作线程
std::thread t(worker, source.get_token());
std::this_thread::sleep_for(std::chrono::seconds(2));
// 主线程发起停止请求
std::cout << "Main thread requesting stop...\n";
source.request_stop();
t.join();
return 0;
}
示例 2:与 std::jthread 深度集成(自动感知与生命周期绑定)
C++20 的 std::jthread 内部原生集成了 std::stop_source。若可调用对象(Task/Function)的首个参数类型为 std::stop_token,std::jthread 会自动传入 token,并在析构时自动触发 request_stop() 并执行 join():
cpp
#include <iostream>
#include <thread>
#include <chrono>
void worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::cout << "Working...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
std::cout << "Cleaning up...\n";
}
int main() {
{
// t 析构时会自动调用 request_stop() 并 join()
std::jthread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(1));
// 亦可显式手动发起:t.request_stop();
} // 超出作用域,自动发起停止请求并阻塞等待线程结束
return 0;
}
三、 架构亮点与设计哲学
1. 读写分离与权责对等(Separation of Concerns)
接口设计严格区分了"控制端"与"执行端":
- 控制端(
stop_source)独占修改权; - 执行端(
stop_token)仅保留只读观察权。
这种编译期强化的类型约束,彻底杜绝了工作线程误触发取消信号的可能性,显著降低了多线程代码的耦合度。
2. 协同式安全与无锁高效设计
- 协同非强制 :区别于 POSIX
pthread_cancel等强制终止线程的危险做法,该机制强制要求工作线程"主动响应",确保了栈展开(Stack Unwinding)与 RAII 资源释放的完整性。 - 高效无锁实现 :底层共享状态建立在原子变量与引用计数之上,拷贝与状态检查均无需显式加锁,拥有极高的并发性能。
3. 解决阻塞响应的"灵魂":std::stop_callback
如果仅有轮询(stop_requested()),当线程处于阻塞(如 sleep、网络 I/O、条件变量等待)时将无法及时响应。
std::stop_callback允许向stop_token注册回调,一旦request_stop()被触发,回调将立即被调用(例如用于中断 Socket 或唤醒条件变量)。- C++20 同步引入了
std::condition_variable_any::wait(lock, stoken, pred),彻底解决了"条件变量阻塞等待与取消信号协同"的难题。
四、 局限性与设计遗憾(踩坑避雷指南)
1. 无法直接中断系统级/C API 阻塞
stop_token 本质上是语言层面的状态标记与回调通知机制,无法做到操作系统内核级的信号中断(Interrupted System Call)。
- 风险点 :若线程阻塞在未封装
stop_callback的传统 POSIXread()/select()或 Win32 阻塞 API 中,单纯查询stop_token无法唤醒线程。必须在stop_callback中手动执行shutdown(fd)或关闭 Handle 来强制打断。
2. 回调函数的同步执行上下文与死锁陷阱(极易踩坑)
stop_callback 默认是在调用 request_stop() 的线程上下文中同步(Synchronously)执行的。
- 死锁风险 :若调用
request_stop()的线程持有互斥锁 A,而stop_callback内部也尝试获取锁 A,将直接引发死锁。 - 性能抖动 :若注册的回调逻辑较为昂贵,会直接阻塞调用
request_stop()的主控线程。
3. 缺乏树状/层级化取消支持(Non-Hierarchical)
标准库的实现是扁平化(Flat)的。在复杂的任务调度或 Actor 模型中,常见的"取消父任务自动取消所有子任务,但取消子任务不影响父任务"的层级取消逻辑(Hierarchical Cancellation),C++20 原生机制并不支持,需要结合图/树结构二次封装。
4. 对传统 std::condition_variable 的兼容性限制
由于历史包袱,传统的 std::condition_variable 硬绑定了 std::unique_lock<std::mutex>。出于 ABI 兼容与性能考虑,它无法直接配合 stop_token 使用。必须切换到通用性更强但开销略高的 std::condition_variable_any。