协程调度器本质是状态机加任务队列,通过std::coroutine_handle手动管理生命周期,核心逻辑为挂起时入队、恢复时出队并resume,需避免重复resume或操作已销毁handle。

协程调度器的核心其实是状态机加任务队列
直接用 std::coroutine_handle 手动管理协程生命周期,配合一个就绪队列(比如 std::queue 或 std::deque),就能跑起来最简调度器。别被“调度器”吓住——它不依赖线程池、不涉及抢占,本质就是「谁该醒了,就 resume 谁」。
关键点在于:协程挂起时必须把 handle 存进队列;恢复时从队列取 handle 并调用 resume();且要避免重复 resume 或对已销毁 handle 操作。
- 必须在
await_suspend里把 handle 入队,不能在协程体里手动 push - 调度器的
run()方法应循环 pop + resume,直到队列为空且无新任务入队 - 每次
resume()后要检查done(),否则可能对已结束协程重复 resume,触发未定义行为
如何让协程自动入队?靠自定义 awaiter 的 await_suspend
标准库没提供开箱即用的“调度友好型 awaiter”,得自己写。重点是让每个协程挂起时,自动把自身 handle 交给调度器。
典型模式是:定义一个 scheduler_awaiter,它的 await_suspend 接收 std::coroutine_handle 参数,内部调用调度器的 schedule() 方法入队。
struct scheduler_awaiter {
bool await_ready() const noexcept { return false; }
void await_resume() const noexcept {}
void await_suspend(std::coroutine_handle h) noexcept {
scheduler::instance().schedule(h); // 入队
}
};
-
await_ready()返回false强制挂起,确保每次 co_await 都走 suspend 流程 - 不要在
await_suspend里直接 resume 其他协程——这会破坏调度顺序,也难 debug - 如果协程在构造时就挂起(比如 co_await 一个立即返回 false 的 awaiter),handle 尚未完全初始化,此时传给调度器可能出问题;稳妥做法是延迟到首次 suspend 才入队
为什么不能直接用 std::this_thread::sleep_for 模拟异步等待?
因为 sleep 是阻塞式,会卡住整个调度器线程,所有协程都停摆。真异步等待必须交由外部事件驱动,比如定时器回调、I/O 完成通知,或——最简单的——用另一个协程 yield 后再 resume。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
想实现 delay,得写一个基于时间轮或优先队列的延时调度器,或者退一步,用 std::chrono + 纯用户态 busy-wait(仅用于学习,别上线):
auto delay(int ms) {
auto start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start
- 上面这种 busy-wait 版本只适合 demo,实际中 CPU 占用 100%,且无法响应真实 I/O
- 真正可用的 delay 必须和底层事件循环联动,比如 libuv 的
uv_timer_t或 io_uring 的 timeout 提交 - 别试图在
await_suspend里开新线程做 sleep 再回调——这违背协程轻量初衷,也容易引发 handle 生命周期错误
调度器实例怎么保证线程安全?多数情况下根本不需要
如果你的调度器只在单线程里运行(比如主线程驱动所有协程),那 std::queue 完全够用,加锁反而是累赘。只有当你明确需要多线程提交任务(比如 worker 线程生成协程,主线程执行),才考虑线程安全。
但注意:即使加了锁,resume 操作本身不是原子的——h.resume() 可能触发协程内再次挂起,导致 handle 被移出队列又重新入队,这时锁粒度不对就会出问题。
- 单线程调度器:用
std::deque替代std::queue,方便前端插入(比如高优先级任务) - 多线程场景下,更推荐「生产者无锁入队 + 消费者单线程 drain」模型,比如用
moodycamel::ConcurrentQueue - 绝对不要在 resume 过程中修改正在运行的协程的局部变量——C++ 协程栈是独占的,跨协程访问需显式同步
最常被忽略的一点:协程销毁时机。如果协程在 await_suspend 里抛异常,handle 不会自动销毁,得确保调度器不会对已异常退出的 handle 调用 resume。最好在 await_resume 或 final_suspend 里做清理。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










