co_await挂起时保存栈帧到promise_type对象并跳转至awaiter::await_suspend(),其返回值决定后续调度;若返回true需确保coroutine_handle生命周期覆盖恢复全程。

co_await 挂起时到底发生了什么
协程挂起不是“暂停执行”,而是保存当前栈帧(局部变量、寄存器状态)到一个堆分配的 promise_type 关联对象里,然后跳转回 awaiter 的 await_suspend()。如果它返回 void 或 bool(且为 false),协程就真停住了;如果返回另一个协程句柄(比如 std::coroutine_handle),调度器会立刻恢复那个协程。
- 常见错误:自定义
awaiter的await_suspend()忘记返回值,导致编译失败或未定义行为 - 关键点:
await_suspend()是唯一能决定“挂起后去哪”的地方,不是await_ready()决定是否挂起,而是它决定挂起后怎么跳 - 性能影响:每次
co_await至少一次虚函数调用(如果awaiter是多态)+ 一次堆内存写入(保存 promise 状态),高频 await 会明显拖慢
为什么 co_await 后没进 await_resume 就崩溃了
崩溃往往发生在 await_suspend() 返回 true(表示手动接管调度)之后,但你没确保被挂起的协程 handle 在恢复前一直有效——比如把它存在栈上、或者在恢复前就被 destroy() 了。
- 典型场景:在 lambda 里捕获
std::coroutine_handle并传给线程池,lambda 执行完就销毁 handle,线程池 later 调用resume()时访问野指针 - 正确做法:把 handle 存到堆对象里(比如
std::shared_ptr包裹的结构体),或确保其生命周期覆盖整个异步流程 - 调试技巧:在
await_suspend()里打日志,在await_resume()前加断点,确认 handle 是否被重复 resume 或已 destroy
std::suspend_always 和 std::suspend_never 的实际区别
它们只是两个预定义的 awaiter 类型,核心差异只在 await_ready() 返回值:suspend_always::await_ready() 总返回 false,强制挂起;suspend_never 总返回 true,跳过挂起直接进 await_resume()。
- 别误用:
suspend_never不等于“同步执行”,它只是跳过挂起逻辑,但await_resume()仍会被调,且此时协程仍在当前线程上下文中 - 兼容性注意:C++20 标准库只保证这两个类型有标准定义,其他类似功能必须自己实现,不能依赖第三方头文件里的同名类型
- 参数差异:二者都不带状态,没有构造参数,也不能传参进去;想带数据得自己写
awaiter类,重载operator co_await
协程恢复后局部变量值“变回初始值”
这不是 bug,是 C++ 协程规范要求:每次挂起后恢复,函数作用域内的局部变量(非 static、非引用捕获)都会被析构再重建——因为协程可能跨线程、跨栈恢复,C++ 不保证原栈帧还存在。
立即学习“C++免费学习笔记(深入)”;
- 真实现象:比如
int x = 42; co_await something; printf("%d", x);输出可能是随机值或 0,不是 42 - 解决方法:把需要保持的状态存在
promise_type成员里,或用static/thread_local(慎用)/ 智能指针堆分配 - 容易踩的坑:lambda 捕获局部变量(如
[x]() mutable { ... }),挂起后恢复时 x 已被析构,访问即 UB
协程切换的本质是控制流转移 + 状态快照,不是线程切换,也不等价于 setjmp/longjmp;所有“意外丢失状态”的问题,基本都源于混淆了栈生命周期和协程生命周期。

















