用途
该技能用于引导代理理解 C++20 协程机制,覆盖 co_await、co_yield、co_return,实现所需的 promise_type,理解协程帧内存布局,在 GDB 中调试挂起的协程,以及降低帧分配开销。
触发场景
资料中列出的典型问题包括:co_await、co_yield、co_return 如何工作;如何为协程实现 promise_type;协程如何挂起与恢复;如何在 GDB 中调试挂起的协程;协程帧占用多少内存;如何用 co_yield 编写生成器。
主要能力
三个协程关键字
- co_return:返回值并结束协程。
- co_yield:产出一个值,挂起,稍后恢复。
- co_await:挂起直到某个 awaitable 完成。
函数只要包含这三个关键字中的任意一个就是协程,其返回类型必须是带 promise_type 的协程类型。
最小协程类型 Task
资料给出了一个模板 Task 示例,包含 promise_type(持有 std::optional<T> value 与 std::exception_ptr exception)、get_return_object、initial_suspend(suspend_always,惰性启动)、final_suspend、return_value、unhandled_exception,以及外部句柄、移动构造、析构时 handle.destroy() 和 get() 恢复执行并重抛异常的逻辑。示例中 Task<int> compute() 通过 co_return 42 返回,main 中调用 task.get() 得到 42。
使用 co_yield 的 Generator
资料给出 Generator 模板示例:promise_type 中 yield_value 保存当前值并返回 suspend_always,从而实现产出后挂起;外部提供迭代器,operator++ 调用 handle.resume() 并更新 done,begin() 先 resume 推进到首个 yield。示例 iota(start, end) 用 co_yield 产出整数,可在范围 for 中遍历输出 0 1 2 3 4。
自定义 co_await 目标(Awaitable)
awaitable 有三个方法:await_ready()(返回 true 表示不挂起)、await_suspend(handle)(挂起并安排恢复)、await_resume()(返回 co_await 表达式的值)。资料给出 TimerAwaitable 示例:延迟小于等于 0 时不挂起,否则在 await_suspend 中启动线程睡眠后调用 h.resume()。同时指出 suspend_always 与 suspend_never 是内置 awaitable。
协程帧布局与内存
编译器分配的协程帧(堆对象)包含:跨挂起点存活的局部变量、promise 对象、当前挂起状态(恢复位置)、指向恢复/销毁函数的指针。资料建议用 Compiler Explorer 配合 g++ -std=c++20 -O2 -S 查看 operator new 调用大小,或用 clang 的 -emit-llvm 查找 coro.size。减少帧体积的方法包括:不要让大对象跨 co_await 存活;在挂起前把需要的数据移出(例如先取 size 并清空 buffer 再挂起)。
在 GDB 中调试挂起的协程
资料列出若干操作:info locals 查看 coroutine_handle 变量;通过 handle 打印 promise 对象;GDB 14+ 提供协程相关支持(info coroutines,为 GCC 实验性支持);用 step/finish/next 单步协程执行与挂起点;用 info threads 和 bt 查看线程池上运行的协程。另建议用 -g -O0 编译并配合 break、info frame、set print pretty 等命令检查;同时提醒挂起的协程在恢复前可能不出现在栈上,需要通过保存的 coroutine_handle 追踪。
Boost.Asio 的 co_spawn 与 co_await
资料给出 co_spawn 示例:在 io_context 上启动协程,使用 net::detached,co_await 串联异步读写而不需要回调嵌套;echo_session 用 co_await socket.async_read_some 与 net::async_write 循环回显。
std::generator(C++23)
用 std::generator<int> 编写 fibonacci 生成器,结合 std::views::take 取前 10 个值。其特点是不需要手动管理协程句柄,由编译器提供 std::generator 的 promise 类型。
编译时间影响
资料说明协程会增加模板实例化与头文件解析成本,并列出缓解方式:-O2 下的 HALO 可减少生成的帧胶水代码;把 co_await 的实现放到 .cpp 中可减少重编译连锁;对协程返回类型使用 Pimpl 可对头文件隐藏 awaitable 模板;使用 ccache 或模块。建议用 g++ -ftime-report 或 clang -ftime-trace 测量,并指出协程密集的头文件(如 Asio)可谨慎使用 unity build,但需与内存占用权衡。
常见陷阱
资料列出若干问题与处理方式:在非协程中使用 co_await,原因是函数缺少协程返回类型,需改为协程返回类型;co_return 后的悬空句柄,原因是协程结束后继续使用句柄,需在 resume 前检查 handle.done();重复恢复,原因是恢复了已恢复的协程,需跟踪状态,仅在挂起时恢复;协程帧从未释放,原因是忘记 handle.destroy(),应使用 RAII 包装(Task、Generator);堆分配开销,原因是每次协程调用都新建帧,可通过 -O2 启用 HALO;递归 co_await 深度,原因是深层链导致栈溢出,可使用 std::coroutine_handle<> 尾调用模式。
相关技能
资料提及可结合 cpp-templates 处理其他 C++20 高级特性,结合 rust-async-internals 对照 Rust 的 Future/Poll 模型,并结合 gdb 技能管理 GDB 会话。
限制
资料未说明该技能的收费方式、价格、支持平台、版本兼容范围与性能效果承诺,这些信息暂未提供。