c++20协程不自动并发,需配合调度器或awaitable封装才能实现异步;其核心是promise_type控制行为,co_await用于等待、co_yield仅用于generator;编译器支持与abi存在差异,调试和发布需注意细节。

协程不是线程,co_await 不会自动并发
很多人一看到“协程”就默认能并行跑任务,结果发现 co_await 之后代码还是顺序执行,甚至比普通函数还慢。这是因为 C++20 协程本身不带调度器——它只提供挂起/恢复的机制,不负责把协程扔给线程池或事件循环。
真正异步,得靠你选的 executor 或库(比如 std::execution、libunifex、cppcoro),或者自己写一个简单的轮询器。标准库目前只提供底层协程支持(promise_type、co_await、co_return),没给现成的 async 或 Task 类型。
- 别直接在主线程里
co_await一个耗时 I/O 操作,除非你有配套的 awaitable 封装(比如基于epoll或IOCP的 awaiter) - 想模拟“异步”,最简方式是用
std::Thread+std::future包一层,但这和协程无关,只是借个壳 - 调试时注意:协程函数返回的是一个对象(如
Task<int></int>),不是值;不co_await它,它根本不会运行
promise_type 是协程行为的开关,不是可选配件
所有协程函数编译后都会隐式实例化一个 promise_type,它控制协程怎么初始化、挂起、异常处理、返回值怎么包装。如果你没显式定义,编译器会尝试找默认的(比如 std::suspend_always),但多数时候找不到,直接报错:
Error: no type named 'promise_type' in 'MyCoroutine'
立即学习“C++免费学习笔记(深入)”;
常见场景是想写一个轻量 Generator<t></t> 或 Task<t></t>,结果忘了在返回类型里嵌套定义 promise_type。
- 必须满足:静态成员
get_return_object()、initial_suspend()、final_suspend()、return_value(T)(或return_void()) -
initial_suspend()返回std::suspend_always表示创建即挂起;返回std::suspend_never表示立即执行到第一个co_await - 漏掉
unhandled_exception()?一旦协程里抛异常,程序直接 terminate
别拿 co_yield 当 co_await 用
co_yield 只用于 generator 类型(比如遍历生成器),它的语义是“产出一个值并挂起”,底层调用的是 promise.yield_value();而 co_await 是通用挂起点,依赖 await_ready/await_suspend/await_resume 三件套。
典型错误:在非 generator 协程里写 co_yield some_awaitable,编译不过,因为 promise_type 没实现 yield_value,而且语义也不对。
-
co_yield x→ 等价于promise.yield_value(x);+ 挂起 -
co_await x→ 触发x.await_ready()等三个函数,和 promise 关系间接 - 想让协程“等一个 future”,必须用
co_await,不是co_yield;generator 里用co_yield是为了迭代,不是为了等待
MSVC 和 Clang 对协程的支持细节差异真不小
Clang 15+ 默认开启协程支持(-std=c++20 -fcoroutines),而 MSVC 从 19.30 开始才稳定支持,且要求 windows SDK ≥ 10.0.20348.0;GCC 目前(13.x)仍实验性支持,需加 -fcoroutines 且禁用 PCH。
更麻烦的是 ABI 兼容问题:不同编译器生成的协程对象内存布局不一致,跨 DLL 边界传 Task<int></int> 很可能崩溃。
- MSVC 下
co_await某些自定义 awaitable 时,若await_suspend返回void,可能静默失败;Clang 要求必须返回bool或coroutine_handle - 调试时,MSVC 的 visual studio 调试器对协程栈帧支持有限,常显示 “
coroutine frame not available” - 发布构建务必关掉
/Zi(PDB 调试信息),否则 MSVC 可能因协程元数据生成巨大 PDB 文件
协程真正的门槛不在语法,而在你得同时理清:promise 控制流、awaiter 状态机、调度时机、以及编译器实际生成了什么对象。少一个环节,就容易卡在“协程没执行”“值没拿到”“程序卡死”这种无提示问题上。