最简c++20协程需clang 13+/msvc 19.30+或gcc 12+,包含promise_type、返回类型(如task)和co_await/co_return函数体,必须正确管理coroutine_handle生命周期以防泄漏。

用 C++20 co_await 写最简协程,必须开 C++20 且选对编译器
标准协程不是“加个关键字就能跑”,clang 13+ 或 MSVC 19.30+ 可以,gcc 12 虽支持但需手动实现 promise type,gcc 11 及更早版本直接不识别 co_await。项目里先确认 g++ -std=c++20 --version 输出是否 ≥12,否则别浪费时间调语法。
最小可运行结构包含三部分:promise_type(定义协程行为)、返回类型(含 get_return_object() 和 initial_suspend())、函数体(含 co_await 或 co_return)。
-
promise_type必须是返回类型的嵌套类,且公开提供get_return_object()、initial_suspend()、final_suspend()、return_void()(或return_value(T)) - 协程函数返回类型不能是
void;常见做法是返回自定义的Task类 - 没实现
await_transform时,co_await后面只能跟已定义await_ready/await_suspend/await_resume的对象
std::suspend_always 和 std::suspend_never 控制挂起时机
这两个是标准库提供的 awaiter,决定协程在进入/退出时是否立刻挂起。比如 initial_suspend() 返回 std::suspend_always{},协程创建后不立即执行,等你显式调用 resume;若返回 std::suspend_never{},协程会从头跑到第一个挂起点(或结束)。
实际调试时容易卡在“协程没执行”,大概率是 initial_suspend() 返回了 std::suspend_always 却忘了调 handle.resume() —— 这不是 bug,是设计如此。
-
final_suspend()返回std::suspend_always:协程结束时不自动销毁,留给外部清理资源(比如回收coroutine_handle) -
final_suspend()返回std::suspend_never:协程栈被自动释放,再访问 handle 会 UB - 不要在
await_suspend里直接调handle.resume(),除非你要实现“同步等待”,否则极易栈溢出
手写一个可 co_await 的简单awaiter:延迟 1 秒再继续
想让协程暂停 1 秒,不能直接写 co_await std::this_thread::sleep_for(1s) —— sleep_for 不是 awaitable。得自己包装:
struct DelayAwaiter {
std::chrono::steady_clock::duration d;
bool await_ready() const noexcept { return d h) {
// 真实项目应投递到线程池或 event loop
std::thread([h, d = this->d] {
std::this_thread::sleep_for(d);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
注意点:
-
await_suspend接收的是std::coroutine_handle,不是当前协程的 handle;它代表被挂起的那个协程 - 上面示例用
detach()是为了最小化依赖,但生产环境必须用调度器管理线程生命周期,否则进程退出时 detached thread 可能 crash - 如果只是测试,把
await_suspend改成std::this_thread::sleep_for(d); h.resume();更直观,但它会阻塞当前线程
协程 handle 泄露是常见内存问题
每个协程实例背后有堆分配的帧(除非启用 stackless 优化),而 coroutine_handle 是裸指针语义——没有 RAII 封装。漏掉 handle.destroy() 就等于内存泄漏,且不会报错。
典型错误模式:把 Task 返回值存在局部变量里,函数返回后 handle 丢失,无法 destroy。
- 务必确保每个
coroutine_handle::from_address(...)或get_return_object()得到的 handle,最终都有一次且仅一次destroy() - 不要用 raw
coroutine_handle做函数参数传递;封装成unique_coroutine(带 move-only 析构)更安全 - 用 AddressSanitizer 编译(
-fsanitize=address)能捕获部分 handle 悬空访问,但检测不到纯泄漏
协程真正难的不是语法,是生命周期和调度模型的匹配;写完一个 co_await 示例后,先检查 handle 是否被正确持有和释放,比验证逻辑正确性还重要。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











