因为std::coroutine_handle不管理状态和唤醒逻辑,仅是轻量包装;需手动确保协程处于suspend状态、内存有效且无并发resume,否则易触发未定义行为。

为什么不能直接用 std::coroutine_handle 手动调度?
因为 std::coroutine_handle 本身不带状态管理或唤醒逻辑,它只是个“指针+虚表”的薄包装。你拿到一个 std::coroutine_handle,调用 .resume() 前必须确保协程处于 suspend 状态、内存未被释放、且没有被并发 resume —— 这些都得自己兜底。
典型错误现象:std::coroutine_handle::resume(): cannot resume an already-resumed or destroyed coroutine,往往是因为忘了检查 .done(),或在 await_suspend() 里误把 handle 存到栈上又提前返回。
- 所有协程对象(即 promise 对象)必须堆分配,或至少生命周期由调度器统一管理
- 每个协程 resume 后若再次 suspend,必须把 handle 交还给调度器,不能丢弃
- 调度器需提供线程安全的入队/出队机制(哪怕单线程也建议用
std::deque避免迭代器失效)
如何设计最小可行的 promise_type?
关键不是实现全部接口,而是只保留调度器真正需要的三件事:构造时注册、挂起时移交控制权、销毁时清理资源。不需要 return_value、unhandled_exception 等——除非你打算支持 co_return 或异常传播。
struct simple_promise {
simple_coro get_return_object() {
return simple_coro(std::coroutine_handle::from_promise(*this));
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept { std::terminate(); }
// 调度器靠这个把协程加进 ready 队列
void return_to_scheduler() {
scheduler::instance().push(handle);
}
std::coroutine_handle handle;
};
注意 handle 成员必须是 public,且在 get_return_object() 中立即保存;否则后续 resume 时无法访问自身 handle。这是容易被忽略的细节:promise 对象和协程帧是绑定的,但 handle 必须显式存一份才能在 final_suspend 或自定义 awaiter 里用。
立即学习“C++免费学习笔记(深入)”;
怎么让协程在 await 时交还控制权给调度器?
核心是写一个自定义 awaiter,其 await_suspend() 把当前协程 handle 推入调度器队列,并返回 false 表示不自动 resume(即彻底移交调度权)。
常见错误:返回 true 或 std::coroutine_handle{},导致协程被立即 resume,调度器完全没机会插手。
-
await_ready()返回false强制走 suspend 流程(简化模型,实际可按需判断) -
await_suspend(h)中调用scheduler::instance().push(h),然后返回false -
await_resume()只需返回 void,因为本例不传递值
struct schedule_awaiter {
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) noexcept {
scheduler::instance().push(h);
}
void await_resume() const noexcept {}
};
调度循环本身要防哪些坑?
最简调度器只需一个 while 循环 pop + resume,但真实场景下必须处理:协程 resume 后可能立刻再次 suspend、可能抛异常、可能 self-destruct(比如 co_return 后进入 final_suspend)。如果调度器不检查 handle.done() 就继续 pop,会 crash。
性能影响:每次 pop 都应从容器头部取(std::deque::front()),避免 vector 的 O(n) 移动;若用 std::queue 包装 deque,记得它默认用 deque 作底层容器,没问题。
- resume 前务必检查
!h.done(),否则 final_suspend 后再 resume 是未定义行为 - resume 后立即检查
h.done(),为 true 则跳过 push 回队列(已结束) - 不要在 resume 内部 catch 异常 —— promise 的
unhandled_exception已接管,强行捕获反而掩盖问题
void scheduler::run() {
while (!ready_queue.empty()) {
auto h = std::move(ready_queue.front());
ready_queue.pop_front();
if (h.done()) continue;
h.resume();
if (!h.done()) {
push(h); // 若仍活跃,放回队尾(轮转调度)
}
}
}
final_suspend 返回 std::suspend_always 是关键:它让协程停在最后一步,把销毁控制权交还给调度器。否则协程帧可能被 runtime 自动释放,而你的调度器还拿着 dangling handle。











