协程切换需手动管理独立栈与上下文,c++20协程不适用传统调度;应使用ucontext_t或boost::context,避免setjmp/longjmp和异常跳转,封装状态机并统一yield,调度器须基于事件驱动而非轮询。

协程切换必须靠栈保存和恢复
协作式调度本质是手动控制函数执行流的暂停与继续,C++ 没有原生协程支持(C++20 协程是 stackless,不适用于传统上下文切换),所以得自己管理栈。关键不是“怎么启动任务”,而是“切出去时当前栈帧怎么存、切回来时怎么还原”。setjmp/longjmp 看似简单,但只保存寄存器上下文,不保存栈内容,跨函数调用后栈指针偏移会导致崩溃。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
ucontext_t(POSIX)或boost::context(跨平台)做栈分配与切换,避免手写汇编 - 每个任务需独立栈空间,大小建议 ≥ 64KB(小了容易栈溢出,尤其递归或 std::string 操作)
- 切换前确保当前栈上无未析构的局部对象(比如
std::vector析构会调 malloc,而 malloc 可能依赖线程局部存储,协作式调度下 TLS 不自动切换)
任务状态机不能靠 return 或异常驱动
有人试图用 return + 重入来模拟挂起,结果发现变量生命周期错乱、this 指针失效;也有人 throw 一个自定义异常再 catch 来跳转,但异常机制开销大,且 longjmp 会绕过栈展开(stack unwinding),导致 RAII 失效、资源泄漏。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 把每个任务封装为状态机类,用
enum class state { ready, running, suspended, done }显式管理 - 挂起点统一放在函数末尾或循环体中,用
yield()主动让出控制权,不要依赖函数自然返回 - 避免在挂起点附近使用带析构逻辑的局部变量(如
std::lock_guard、临时std::thread),改用裸指针或延迟释放
调度器必须显式控制 resume 时机
协作式调度没有抢占,意味着任务不主动 yield(),调度器就永远等不到控制权。常见错误是把 I/O 等待写成 while 循环轮询,CPU 占满还卡死整个调度器。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有阻塞操作(文件读、网络收包、sleep)必须抽象为“可等待事件”,由调度器统一监听(如 epoll/kqueue +
timerfd) - 每个任务挂起时注册回调或事件句柄,而不是自己 sleep;调度器在
epoll_wait返回后批量 resume 就绪任务 -
sleep_for类接口应转为定时器事件,底层调用timerfd_settime,而非usleep—— 后者会让整个进程休眠,破坏协作性
C++20 协程不适合直接用于协作式任务调度
有人看到 co_await 就以为能直接替换老式协程,结果发现 std::coroutine_handle 默认共享主线程栈,无法实现真正的上下文隔离;promise_type 的 await_suspend 只能返回 void 或另一个 handle,没法插入调度队列逻辑。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 若坚持用 C++20 协程,必须配合自定义内存分配器 + 栈切换库(如
libcoro),否则只是语法糖,不是上下文切换 - 标准库
std::generator和std::task(非标准)都假设运行在 thread-per-task 或 event-loop 上,不提供栈管理能力 - 生产环境更推荐
boost::asio::spawn+boost::context组合,它把栈分配、切换、调度队列全封装好了,且兼容 C++11+
真正麻烦的从来不是“怎么切”,而是“切完之后谁负责清理栈、谁保证 TLS 正确、谁防止信号中断破坏上下文”——这些细节不出现在 demo 里,但一上线就炸。










