C++20协程通过co_await等关键字将异步逻辑显式建模为可挂起的表达式求值,编译器自动生成状态机管理局部变量和控制流,但需注意内存分配、异常安全、调度语义及调试限制。

协程让异步代码写得像同步代码一样自然
传统回调或 std::future 写异步逻辑时,控制流被拆得支离破碎:状态要手动保存、错误要层层传递、资源生命周期难管理。C++20 协程通过 co_await、co_yield、co_return 把挂起/恢复点显式标记出来,编译器自动生成状态机,把堆栈局部变量“冻结”进协程帧(coroutine frame)里——你不用手写状态枚举、switch 分支、上下文指针传递。
co_await 的核心不是“等待”,而是“可挂起的表达式求值”
co_await 不是语法糖,它要求操作数提供 await_ready()、await_suspend()、await_resume() 三个成员函数。这意味着你能控制:什么时候真正挂起、挂起后把控制权交给谁(比如调度器)、恢复时返回什么值。
- 常见误区:以为
co_await some_task等同于 “等它完成”,其实它可能立刻返回(await_ready() == true),也可能挂起并调用await_suspend()去注册回调 - 典型实现中,
await_suspend()返回void表示立即挂起;返回bool可决定是否继续执行(true表示已安排好唤醒,当前协程可安全暂停;false表示不挂起,继续跑) - 不要在
await_resume()里抛异常——它运行在恢复上下文中,异常传播路径和普通函数不同,容易导致未定义行为
协程帧内存分配不可忽略,尤其在嵌入式或高频场景
默认情况下,协程帧由 operator new 在堆上分配,且无法被编译器优化掉。这对性能敏感场景(如网络包处理、实时音频)是隐患。
- 可用
promise_type::get_return_object_on_allocation_failure()拦截分配失败,但 C++20 标准没强制要求实现该机制,实际依赖编译器支持(如 MSVC 支持,GCC 12+ 部分支持) - 更可靠的方式是重载
operator new和operator delete在 promise type 中,绑定到对象池或栈内存(需确保生命周期可控) - Clang/GCC 编译时加
-fsanitize=coroutine可检测协程帧生命周期误用,比如挂起后 promise 对象已被析构
协程不是万能的,别用它替代线程或简单循环
协程本质是用户态协作式调度,不解决 CPU 密集型任务的并行问题。它适合 I/O 等待、事件驱动、生成器这类“逻辑上需中断、物理上不占 CPU”的场景。
立即学习“C++免费学习笔记(深入)”;
- 若在
co_await后面直接跟一个忙等待循环(如while(!ready) {}),协程不会挂起,反而阻塞整个线程——这违背了协程设计初衷 - 多个协程共享同一线程时,一个协程长时间不
co_await,会饿死其他协程;没有内置抢占机制,必须靠程序员主动让出控制权 - 调试困难:GDB/LLDB 对协程帧的支持仍有限,
bt可能只显示__builtin_coro_resume,看不到原始调用链
struct task {
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
};
task example() {
std::cout << "before await\n";
co_await std::suspend_always{}; // 挂起
std::cout << "after await\n"; // 恢复后才执行
}
协程的价值不在语法炫技,而在于把“等待外部事件”这件事从控制流中解耦出来。但它的复杂性也藏在细节里:内存模型、异常安全、调试可见性、调度策略——这些地方稍不注意,就比手写状态机还难排查。










