C++20 协程入门与实践
C++20 协程入门与实践
时间:2026/04/09
关键词:
co_await、co_return、co_yield、promise_type、挂起点、异步流程、generator
核心目标:理解协程为什么不是“更轻的线程”,而是一种把异步控制流写成顺序代码的语言机制。
1. 协程到底解决什么问题
传统异步代码常见两个问题:
- 回调层层嵌套
- 状态机代码难写难读
协程的核心价值是:
- 把“会暂停、稍后恢复”的流程写成看起来接近顺序代码的形式
所以它更像:
- 语言级状态机生成器
而不是线程替代品。
2. 协程不是线程
这点最重要。
协程:
- 默认不并行
- 默认不自动切线程
- 只是能挂起和恢复
线程:
- 是操作系统调度实体
- 真正涉及并行执行
所以:
- 协程解决的是“控制流组织”
- 线程解决的是“执行资源”
3. 三个关键字
3.1 co_await
等待某个可等待对象,并可能挂起当前协程。
3.2 co_return
从协程返回结果。
3.3 co_yield
常用于生成器场景,逐个产出值。
4. 编译器视角下协程发生了什么
当函数里出现协程关键字后,它会被编译器改写成:
- 一个协程状态对象
- 一个
promise_type - 若干挂起点
- 一个恢复入口
也就是说,协程本质上是:
- 编译器自动帮你拆出来的状态机
5. promise_type 是什么
如果一个返回类型想作为协程返回对象,就需要配套定义:
promise_type
它负责描述:
- 协程如何创建
- 如何返回值
- 初始/最终是否挂起
- 异常怎么处理
这是 C++ 协程里最“底层”的部分。
6. 一个最小协程返回类型骨架
1 |
|
这样一个返回 Task 的函数就可以写成协程。
7. co_await 的直觉
写:
1 | co_await something; |
编译器会尝试把 something 变成一个 awaitable,并调用:
await_ready()await_suspend()await_resume()
可以粗略理解为:
- 先问要不要挂起
- 如果挂起,如何安排恢复
- 恢复后返回什么结果
8. generator 场景为什么适合协程
例如想逐个生成值:
- 传统写法要手工保存状态
- 协程可以自然写成“产出一个,暂停,再继续”
这正是 co_yield 最直观的用法。
所以协程特别适合:
- lazy sequence
- parser
- pipeline
- 异步流
9. 协程最常见的工程用途
9.1 异步 I/O
把:
- 发请求
- 等待回包
- 继续处理
写成线性流程。
9.2 生成器
按需逐个产出数据。
9.3 Actor / 任务系统
用协程表达暂停与恢复点。
10. 协程为什么很容易“看起来简单,实际上不简单”
因为源码很线性,但真实问题仍然存在:
- 生命周期谁管
- 在哪条线程恢复
- 恢复时机谁触发
- 异常怎么传播
- 取消怎么处理
也就是说:
- 协程简化的是控制流表达
- 没有消灭异步系统的本质复杂度
11. 一个最重要的工程问题:恢复在哪发生
协程本身不决定线程。
真正决定“恢复在哪个执行器/事件循环/线程池”的,是 awaitable 或运行时框架。
所以写协程框架时一定要搞清楚:
- 谁调
resume - 什么时候调
- 在哪调
12. 常见坑
12.1 把协程当轻量线程
这是最常见误解。
12.2 忽略返回对象和 promise 生命周期
协程帧如果没人管理,容易泄漏或悬空。
12.3 只学语法,不理解恢复机制
这样很快就会在真实异步项目里迷路。
12.4 在协程里捕获悬空引用
因为协程可能挂起很久,引用生命周期尤其要小心。
13. 一页总结
协程最重要的理解链是:
- 协程不是线程,而是可挂起函数
- 编译器会把协程改写成状态机
co_await的核心是等待、挂起和恢复- 真正工程难点在生命周期、调度器和恢复时机
如果只记一句:
协程的价值是把异步流程写直,而不是把并发问题自动解决掉。