协程是编译器生成的状态机而非语法糖;co_await被编译为保存局部变量、记录跳转点、返回控制权三步操作;需正确使用suspend_always/suspend_never、自定义promise_type关键函数,并避免临时对象地址跨挂起传递。

协程不是语法糖,是状态机编译器
标准 C++20 协程(co_await / co_yield / co_return)底层不运行在独立线程上,也不依赖运行时调度器;它由编译器把函数体自动拆成状态块 + 一个隐式状态机结构体。你写的每个 co_await 都会被替换成「保存当前栈帧局部变量 → 记录跳转点 → 返回控制权」的三步操作。这意味着:不用手写 switch (state) { case ... },也不用维护一堆 enum State 和 void* context。
用 std::suspend_always 控制挂起点,别碰 std::suspend_never
初学者常误以为「挂起越少性能越好」,结果在关键路径用了 std::suspend_never,导致协程无法暂停、状态无法持久化,后续 co_await 直接崩溃或行为未定义。真实异步状态机需要明确知道「哪里该停、停完谁来 resume」:
-
std::suspend_always是最安全的默认选择,适合所有需跨事件循环迭代的状态节点(比如等待 I/O 完成、定时器触发) - 若某个 awaiter 总是立刻就绪(如已缓存的计算结果),可返回
std::suspend_never,但必须确保其await_resume()不抛异常、不访问已销毁对象 - 永远不要在同一个协程里混用
suspend_always和suspend_never做条件分支——编译器生成的状态机布局会错乱,调试时gdb看不到完整调用栈
自定义 promise_type 必须重载 get_return_object() 和 unhandled_exception()
协程返回类型(如 Task<int></int>)是否能正常构造、异常是否被吞掉,全取决于你写的 promise_type。漏掉任一关键函数,轻则协程启动即 crash,重则异常静默丢失:
-
get_return_object()必须返回一个能持有 promise 指针的对象(通常是包装类),否则co_return后无法获取结果 -
unhandled_exception()必须至少调用std::current_exception()存起来,否则co_await中抛出的异常直接终止整个线程 - 别忘了声明
initial_suspend()和final_suspend()——前者决定协程启动时是否立即挂起(通常返回std::suspend_always),后者决定结束时是否允许销毁 promise(必须返回std::suspend_always,否则 promise 对象可能被提前析构)
用 co_await 替换 while+switch 的典型模式
传统状态机常见写法:while (running) { switch(state) { case CONNECTING: ... break; case HANDLING: ... break; } }。改成协程后,每个状态变成一个 co_await 表达式,逻辑线性展开:
立即学习“C++免费学习笔记(深入)”;
Task<bool> handle_connection() {
co_await connect(); // 隐含 state = CONNECTING
co_await handshake(); // 隐含 state = HANDSHAKING
co_await read_header(); // 隐含 state = READING_HEADER
co_await dispatch(); // 隐含 state = DISPATCHING
co_return true;
}
注意:每个 co_await 后面的语句,都运行在「恢复后的栈上下文」中,局部变量自动延续,不需要手动保存/恢复 buffer、offset 等中间状态。但这也意味着:别在 co_await 表达式里传入临时对象地址(如 &local_var),resume 时该内存早已释放。
协程重构真正的难点不在语法,而在判断哪些状态转移必须可中断、哪些 awaiter 的生命周期必须严格绑定到 promise 存活期——这两点一旦错,core dump 就在 resume 的那一行。










