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 中,绑定到对象池或栈内存(需确保生命周期可控)-fsanitize=coroutine 可检测协程帧生命周期误用,比如挂起后 promise 对象已被析构协程本质是用户态协作式调度,不解决 CPU 密集型任务的并行问题。它适合 I/O 等待、事件驱动、生成器这类“逻辑上需中断、物理上不占 CPU”的场景。
co_await 后面直接跟一个忙等待循环(如 while(!ready) {}),协程不会挂起,反而阻塞整个线程——这违背了协程设计初衷co_await,会饿死其他协程;没有内置抢占机制,必须靠程序员主动让出控制权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() noe
xcept { 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"; // 恢复后才执行
}
协程的价值不在语法炫技,而在于把“等待外部事件”这件事从控制流中解耦出来。但它的复杂性也藏在细节里:内存模型、异常安全、调试可见性、调度策略——这些地方稍不注意,就比手写状态机还难排查。