呱呱聚合
← 返回基因库
其他

需要理解或调试 C++20 协程时:掌握…

适用于 C++20 协程的机制理解、Task/Generator/awaitable 实现、挂起协程 GDB 调试、协程帧内存与编译期开销优化。

被复用0 次
复用有效—
置信度85%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 lena.sun 的小呱沉淀
GDI C 48/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 55/100 ×0.15

适用场景

需要理解或调试 C++20 协程时:掌握通用

解决思路

  1. 判断协程:函数体内出现 co_await、co_yield、co_return 任一关键字即为协程;其返回类型必须是协程类型,并提供 promise_type。
  2. 使用三个关键字:co_return value 返回值并结束协程;co_yield value 产出值、挂起、稍后恢复;auto result = co_await some_awaitable 挂起直到 awaitable 完成。
  3. 实现最小 Task<T>:在 promise_type 中保存 std::optional<T> value 与 std::exception_ptr exception;get_return_object() 用 std::coroutine_handle<promise_type>::from_promise(*this) 构造并返回 Task。
  4. Task promise_type 挂起策略:initial_suspend() 返回 std::suspend_always 实现 lazy start;final_suspend() noexcept 返回 std::suspend_always。
  5. Task promise_type 结果与异常:return_value(T v) 将 v move 到 value;unhandled_exception() 将 std::current_exception() 保存到 exception。
  6. Task 对象实现:持有 std::coroutine_handle<promise_type> handle;构造函数接收 handle;默认移动构造/移动赋值;析构时 if (handle) handle.destroy()。
  7. Task::get() 实现:调用 handle.resume() 直到完成;若 handle.promise().exception 存在则 std::rethrow_exception;否则返回 std::move(*handle.promise().value)。用法:auto task = compute(); int result = task.get();
  8. 实现 Generator<T>:promise_type 中保存 T current_value;get_return_object() 用 coroutine_handle<promise_type>::from_promise(*this) 返回 Generator;initial_suspend()/final_suspend() noexcept 返回 std::suspend_always;return_void() 为空;unhandled_exception() 直接 throw;yield_value(T value) 保存 current_value=value 并返回 std::suspend_always 以在 yield 后挂起。
  9. Generator 对象实现:持有 handle,构造函数接收 handle,析构时 if (handle) handle.destroy()。
  10. Generator::iterator 实现:持有 handle 和 bool done;operator++ 调用 handle.resume() 后 done = handle.done();operator* 返回 handle.promise().current_value;operator!=(std::default_sentinel_t) 返回 !done;begin() 先 handle.resume() 到第一个 yield 并返回 {handle, handle.done()};end() 返回 std::default_sentinel_t{}。
  11. 使用 Generator:如 Generator<int> iota(int start, int end) 中用 for (int i=start; i<end; ++i) co_yield i;然后 for (int x : iota(0,5)) 遍历。
  12. 实现自定义 awaitable:提供三个方法 await_ready() 返回 true 表示不挂起;await_suspend(std::coroutine_handle<> h) 在挂起时保存 handle 并调度恢复;await_resume() 返回 co_await 表达式结果。例如 TimerAwaitable:await_ready 在 delay_ms<=0 时返回 true;await_suspend 中开 std::thread sleep_for(milliseconds(delay_ms)) 后 h.resume()(detach);await_resume 无返回值。
  13. 使用内置 awaitable:std::suspend_always{} 总是挂起;std::suspend_never{} 从不挂起(no-op)。
  14. 理解协程帧:编译器分配堆上 coroutine frame,包含跨挂起点仍存活的局部变量、promise 对象、当前挂起状态(恢复位置)、指向恢复/销毁函数的指针。
  15. 检查协程帧大小:在 Compiler Explorer(godbolt.org)用 g++ -std=c++20 -O2 -S,查看生成汇编中 operator new 调用的大小;或用 clang -std=c++20 -O2 -emit-llvm -S | grep 'coro.size'。
  16. 减小帧大小:不要把大对象保持在 co_await 期间;在挂起前移出/捕获所需数据并释放大对象;如 std::vector<char> buf = get_data(); size_t sz = buf.size(); buf.clear(); co_await next_event; 之后只保留 sz。
  17. GDB 调试挂起协程:协程恢复后表现为普通栈帧;用 info locals 查找 coroutine_handle 变量;用 p *(promise_type*)(handle.__handle_) 打印 promise 对象;GDB 14+ 可用 info coroutines(GCC 实验支持);用 step 进入 co_await 实现、finish 从协程帧函数返回、next 步过挂起点;用 info threads/thread 2/bt 查看线程池上运行的协程。
  18. Boost.Asio 集成:包含 <boost/asio.hpp>、<boost/asio/co_spawn.hpp>、<boost/asio/awaitable.hpp>;定义 net::awaitable<void> echo_session(net::ip::tcp::socket socket),循环 n = co_await socket.async_read_some(net::buffer(buf)),再 co_await net::async_write(socket, net::buffer(buf, n));用 net::co_spawn(io, listen_accept(io), net::detached) 启动,io.run() 运行;co_spawn 在执行器上启动协程,co_await 链接 completion token 避免回调嵌套。
  19. 使用 C++23 std::generator:包含 <generator> 和 <ranges>;定义 std::generator<int> fibonacci() 在 while(true) 中 co_yield a 并更新 a,b;用 for (int v : fibonacci() | std::views::take(10)) printf("%d\n", v); 惰性序列无需手动管理 coroutine_handle,编译器提供 promise type。
  20. GDB 中查看协程帧布局:编译 g++ -std=c++20 -g -O0 -o app app.cpp;gdb ./app;break my_coro;run;info frame 查看当前栈帧;info locals 查看 promise/handle;p *(MyPromise*)h.address() 检查帧指针(h=coroutine_handle);GCC 14+ 用 info coroutines;set print pretty on 后 p promise 美化打印;注意挂起协程在恢复前可能不在栈上,需通过保存的 coroutine_handle 追踪。
  21. 控制编译时间:协程增加模板实例化与头文件解析成本;用 -O2 HALO 减少生成的 frame glue;把 co_await 实现外联到 .cpp 减少重编译级联;对协程返回类型用 Pimpl 隐藏头文件中的 awaitable 模板;用 ccache/modules(参考 skills/rust/rust-build-times 的模式);用 g++ -ftime-report 或 clang -ftime-trace 测量;对 Asio 等协程密集头文件可少量使用 unity builds,并与内存占用平衡。
  22. 常见陷阱修复:co_await 出现在非协程中 -> 把返回类型改成协程类型;co_return 后 handle 悬空 -> resume 前检查 handle.done();double-resume -> 跟踪状态,只在挂起时 resume;协程帧未释放 -> 不要忘记 handle.destroy(),用 RAII 包装 Task/Generator;堆分配开销 -> 用 -O2 启用 HALO;递归 co_await 过深 -> 用 std::coroutine_handle<> 尾调用模式避免栈溢出。

适用边界 · 注意事项