std::coroutine_handle不能直接用于调度,因为它仅是协程状态的指针,不包含唤醒、排队、线程绑定或异常处理等调度逻辑;裸调resume()易因生命周期错误、栈销毁或并发问题导致崩溃。

为什么不能直接用 std::coroutine_handle 自己调度
因为 std::coroutine_handle 只是协程状态的“指针”,不带调度逻辑——它不负责唤醒、排队、线程绑定或异常传播。你调用 resume() 时,如果此时协程正被其他线程挂起、或栈已销毁、或调度器没准备好,就会崩(常见报错:segmentation fault 或 pure virtual method called)。真实调度必须自己管生命周期、就绪队列和执行上下文。
实操建议:
- 所有
coroutine_handle必须在 resume 前检查done() == false,否则 resume 已结束协程会 UB - 不要把
coroutine_handle存在栈上然后跨 await 点使用;容易悬垂 - 调度器启动后,应确保每个 resume 都发生在同一线程(单线程调度器)或加锁/无锁队列(多线程),不能裸调
resume()
最简可行调度器:单线程 + 就绪队列 + run() 循环
不需要线程池、IO 多路复用、优先级——先让 co_await 能跑起来。核心就是两个东西:一个 std::queue<coroutine_handle>></coroutine_handle> 存待执行协程,一个 run() 方法不断 pop + resume。
实操建议:
- 就绪队列用
std::deque比std::queue更稳,避免某些 STL 实现中 move 构造导致 handle 失效 -
run()循环里每次 resume 前要检查 handle 是否有效,可用if (h) h.resume(); - 协程首次 resume 应由调度器触发(比如从
spawn()推入队列),不要靠用户手动调handle.resume() - 示例片段:
void spawn(coroutine_handle h) { ready_queue.push(h); }<br>void run() { while (!ready_queue.empty()) { auto h = ready_queue.front(); ready_queue.pop(); h.resume(); } }
await_suspend 怎么把协程交给调度器
这是协程挂起时的“注册点”。你写的 awaiter 在 await_suspend 里拿到 coroutine_handle,然后扔给调度器的就绪队列或延时队列。关键不是“怎么挂起”,而是“挂起后谁来 resume 它”。
实操建议:
- 返回
void:表示你已自行安排 resume(比如推入调度器),协程立刻挂起 - 返回
bool:true 表示已安排,false 表示不挂起(继续执行)——别乱返回 false,容易逻辑跳过 - 别在
await_suspend里直接调resume(),这会破坏挂起语义,导致栈溢出或死循环 - 如果调度器是全局单例,这里可以直接调
g_scheduler.spawn(h);如果是 per-task 的,需把调度器指针存进 promise_type
promise_type 里必须重载哪些函数
决定协程怎么创建、怎么销毁、挂起失败怎么办。最小集是三个:get_return_object()、initial_suspend()、final_suspend()。少一个都可能 crash 或内存泄漏。
实操建议:
-
get_return_object()必须返回一个包装了coroutine_handle的对象(比如task<t></t>),且该对象能访问 promise(通常用handle.from_promise(*this)) -
initial_suspend()返回suspend_always{},否则协程一创建就执行,调度器来不及接管 -
final_suspend()必须返回suspend_always{},否则协程结束时自动销毁 handle,而你的调度器可能还持有它(use-after-free) - 务必实现
unhandled_exception(),至少std::terminate(),否则协程里抛异常会静默崩溃
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











