• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-07-11 11:22 Aet 隐藏边栏 |   抢沙发  2 
文章评分 1 次,平均分 5.0

协程

概述

  1. C++20 协程(Coroutines)是一种无栈协程实现,通过挂起/恢复机制简化异步编程,将回调式代码转化为顺序式结构
  2. 使用C++20协程时,必须实现promise类型(协程的行为控制器)和可选的awaitable对象(挂起/恢复行为控制器)

机制

  1. 无栈设计
    1. 协程状态存储在堆上(协程帧),而非调用栈,节省内存且切换开销低
  2. 关键字
    1. co_await:挂起协程,等待异步操作完成(如I/O请求)
    2. co_yield:生成值并挂起,用于序列生成器(如迭代器)
    3. co_return:结束协程并返回值
  3. 执行流程
    1. 协程通过多次挂起/恢复实现协作式多任务,由用户态调度,避免内核线程切换开销

核心组件

  1. 协程帧
    1. 作用:存储协程状态,包括局部变量、参数、挂起点位置等
    2. 生命周期:在首次调用时创建,结束时销毁(需手动管理内存)
  2. promise类型(定义协程行为,需实现以下接口:)
    1. get_return_object():返回给调用者的对象(如任务句柄)
    2. initial_suspend()/final_suspend():控制协程开始/结束时是否挂起(返回 std::suspend_alwayssuspend_never
    3. return_value(T)/return_void():处理 co_return 返回值
    4. yield_value(T):支持 co_yield 的值传递
    5. unhandled_exception():异常处理逻辑
  3. 协程句柄(std::coroutine_handle
    1. 操作协程的生命周期:
    2. resume():恢复挂起的协程
    3. destroy():销毁协程帧
    4. done():检查协程是否结束
    5. promise():获取关联的 promise 对象
  4. Awaitable 接口(co_await 的操作对象需实现:)
    1. await_ready():返回 false 时触发挂起
    2. await_suspend(handle):挂起时执行(可调度其他任务)
    3. await_resume():恢复时返回结果
    4. 内置类型:
      std::suspend_always:总是挂起
      std::suspend_never:不挂起

必须实现的函数及结构

  1. Promise 类型必须实现的函数
函数签名 调用时机 必选/可选 典型实现
get_return_object() 协程开始执行前 必须 返回给用户的协程句柄封装对象
initial_suspend() 协程初始执行前 必须 返回 suspend_always(挂起) 或 suspend_never(不挂起)
final_suspend() co_return 执行后 必须 通常返回 suspend_always 以便获取结果
unhandled_exception() 协程内抛出未捕获异常时 必须 处理异常(如记录日志、设置异常状态)
return_void() co_return; 调用时 二者选一 处理无返回值返回
return_value(T) co_return value; 调用时 二者选一 处理有返回值返回
yield_value(T) co_yield value; 调用时 可选 生成器场景必须实现
  1. Awaitable 对象必须实现的函数
函数签名 作用
bool await_ready() const 是否无需挂起(true=立即执行)
void await_suspend(coro_handle) 挂起时执行的逻辑(通常安排恢复机制)
T await_resume() 恢复时返回结果(T可为void)

PromiseAwaitable 的关系与区别

  1. promise(协程的"大脑")
    1. 控制协程的生命周期(初始/最终挂起)
    2. 管理返回值/异常
    3. 定义协程的返回类型
  2. Awaitable 对象(挂起点的"控制器")
    1. 定义 co_await 的具体行为
    2. 管理挂起期间的异步操作
    3. 决定何时/如何恢复协程
  3. 场景对比
特征 Promise 类型 Awaitable 对象
实现位置 与协程返回类型关联 独立类型
必要接口 5个必须实现的函数 3个必须实现的函数
调用方 编译器自动调用 通过 co_await 显式调用
典型应用 定义协程基本行为 实现特定挂起/恢复逻辑
示例 控制协程初始挂起、异常处理 网络I/O、定时器、锁等待
  1. 流程与交互

promise类型实现

co_await

  1. 挂起当前协程,等待异步任务完成后恢复协程

co_yield

  1. 概述
    1. 是协程中实现"生成器模式"的关键机制
    2. 允许协程在返回一个值后暂停执行,后续需要时可以恢复执行并产生下一个值
  2. 关键行为
    1. 当协程执行到 co_yield 时:
    2. 暂停协程(当前执行位置被保存)
    3. 返回指定值 给调用者
    4. 等待调用者的"获取下一个值"请求
    5. 再次被调用时从上次暂停的位置恢复执行
  3. 示例文件处理
    1. 可以理解为:协程函数read_large_file返回了一个生成器,每次调用next的时候该生成器会返回数据,然后暂停协程,直到下一次被请求(也就是下一次next被调用)

  1. 上述文件处理方式和常规的读取文件的方式(也是在一个循环中,读取完一块再读取下一块数据)有什么不一样的?
    1. 控制流模式不一样
      传统方式的循环是:读取器主动推送数据给处理器
      协程生成器的方式是:处理器按需从读取器拉取数据
    2. 资源管理差异
      传统方式的循环是:文件在读取期间全程保持打开
      传统方式的循环是:整个文件处理过程是原子操作
      传统方式的循环是:无法中途暂停释放资源
      协程生成器方式是:协程暂停时不会自动关闭文件,需要显式管理
    3. 并发能力
      传统方式的循环是:传统循环无法实现这种单读取器多处理器的并行架构
      协程生成器方式是:协程生成器的方式可以,见下面示例代码
    4. 处理流程灵活性
      传统方式的循环是:传统循环无法实现这种分段处理
      协程生成器方式是:协程生成器的方式可以做到,见下面示例代码

问题

  1. 挂起协程
    1. 协程挂起就是暂停执行的意思
    2. 协程在特定点停止执行,保存当前完整状态,并在稍后从完全相同的状态恢复执行
  2. 为什么要挂起协程
    1. 等待异步操作
    2. 资源暂时不可用
    3. 主动让出控制权
    4. 构建生成器
  3. 调用协程函数就是启动了C++20协程吗
    1. 调用协程函数就是启动了C++20协程的生命周期,但具体是否会立即执行函数体代码,取决于在协程设计中是否在启动点设置了挂起
  4. 对协程的类比理解:
    1. 相当于启动了一个线程执行异步任务,并且无回调,然后对该线程调用了detatch
    2. 不会阻塞主线程(协程也不阻塞主线程)
    3. 无回调,该线程自动结束(协程也自动结束)
    4. 如果要等待异步任务结束,要在主程序结束之前对线程进行等待(协程也需要等待)
  5. co_awaitco_yield
    1. 见上文相关小节
  6. 多个协程函数在一个线程上怎么并行
    1. 在单线程上运行多个协程时,它们并非真正的并行(parallel),而是通过协作式多任务(cooperative multitasking)实现的并发(concurrency

总结

  1. 要使用C++20的协程,就得实现一个自定义的promise类型(该类型有5个必要的成员函数必须实现)作为协程函数的返回类型
  2. 要在这个协程函数中对挂起点逻辑进行控制,就得实现一个自定义的awaitable类型(该类型有3个必要的成员函数必须实现),用于在协程函数中使用
  3. 另外,要用一个包装类包含一个名为 promise_type 的嵌套类型,用promise_type将这个包装类和自定义promise类型关联起来

结构

协程:应用

协程句柄操作

awaitable高级实现

任务调度系统实现

带超时的awaitable

协程同步原语:协程锁

HTTP服务器协程化

协程管道模式

协程帧内存池

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享