当前位置:网站首页 > 技术博客 > 正文

协程到底是什么



协程是能暂停执行以在之后恢复的函数。协程是无栈的:它们通过返回到调用方暂停执行,并且从栈分离存储恢复所要求的数据。这允许编写异步执行的顺序代码(例如不使用显式的回调来处理非阻塞 I/O),还支持对惰性计算的无限序列上的算法及其他用途。

若函数的定义做下列任何内容之一,则它是协程:

  • 用 运算符暂停执行,直至恢复
  • 用关键词 暂停执行并返回一个值
  • 用关键词 完成执行并返回一个值

每个协程必须具有能够满足一组要求的返回类型,标注于下。

协程不能使用变长实参,普通的 return 语句,或占位符返回类型( 或 )。

constexpr 函数、构造函数、析构函数及 函数 不能是协程。

每个协程均与下列对象关联

  • 承诺(promise)对象,从协程内部操纵。协程通过此对象提交其结果或异常。
  • 协程句柄 (coroutine handle),从协程外部操纵。这是用于恢复协程执行或销毁协程帧的非拥有柄。
  • 协程状态 (coroutine state),它是一个包含以下各项的分配于堆(除非优化掉其分配)的内部对象
  • 承诺对象
  • 各个形参(全部按值复制)
  • 当前暂停点的某种表示,使得恢复时程序知晓要从何处继续,销毁时知晓有哪些局部变量在作用域内
  • 局部变量和临时量,其生存期跨过当前暂停点

当协程开始执行时,它进行下列操作:

  • 用 分配协程状态对象(见下文)
  • 将所有函数形参复制到协程状态中:按值传递的形参被移动或复制,按引用传递的参数保持为引用(因此,若在被指代对象的生存期结束后恢复协程,其可能变为悬垂引用)
  • 调用承诺对象的构造函数。若承诺类型拥有接收所有协程形参的构造函数,则以复制后的协程实参调用该构造函数。否则调用其默认构造函数。
  • 调用 并将其结果在局部变量中保持。该调用的结果将在协程首次暂停时返回给调用方。至此并包含这个步骤为止,任何抛出的异常均传播回调用方,而非置于承诺中。
  • 调用 并 其结果。典型的 Promise 类型,要么(对于惰性启动的协程)返回 ,要么(对于急切启动的协程)返回 。
  • 当 恢复时,开始协程体的执行。

当协程抵达暂停点时

  • 将先前获得的返回对象返回给调用方/恢复方,若需要则先隐式转换到协程的返回类型。

当协程抵达 语句时,它进行下列操作:

  • 对下列情形调用
  • ,其中 expr 具有 void 类型
  • 控制流出返回 void 的协程的结尾。此情况下,若 Promise 类型无 成员函数,则行为未定义。
  • 或对于 调用 ,其中 expr 具有非 void 类型
  • 以创建的逆序销毁所有具有自动存储期的变量。
  • 调用 并 其结果。

若协程因未捕捉的异常结束,则它进行下列操作:

  • 捕捉异常并在 catch 块内调用
  • 调用 并 其结果(例如,以恢复某个继续或发布其结果)。从此点之后,恢复协程是未定义行为。

当经由 或未捕捉异常而终止协程导致协程状态被销毁,或经由其句柄而导致其被销毁时,它进行下列操作:

  • 调用承诺对象的析构函数。
  • 调用各个函数形参副本的析构函数。
  • 调用 以释放协程状态所用的内存。
  • 转移执行回到调用方/恢复方。

协程状态由非数组 在堆上分配。

若 类型定义了类级别的替代函数,则将使用它,否则将使用全局的 。

若 类型定义了接收额外形参的 的布置形式,而它们所匹配的实参列表中,第一实参是要求的大小( 类型),而其余则是各个协程函数实参,则将这些实参传递给 (这使得能对协程使用前导分配器约定)

以下情况下,可以优化掉对 的调用(即使使用了自定义分配器):

  • 协程状态的生存期严格内嵌于调用方的生存期,且
  • 协程帧的大小在调用点已知

该情况下,协程状态嵌入调用方的栈帧(若调用方是普通函数)或协程状态(若调用方是协程)之中。

若分配失败,则协程抛出 ,除非 Promise 类型定义了成员函数 。若定义了该成员函数,则使用 的 形式进行分配,而在分配失败时,协程立即将从 获得的对象返回给调用方。

编译器用 从协程的返回类型确定 类型。

正式而言,令 与 分别代表协程的返回类型与参数类型列表, 与 (若存在)分别代表协程所属的类与其 cv 限定,若定义它为非静态成员函数,以如下方式确定其 类型:

  • ,若不定义协程为非静态成员函数,
  • ,若定义协程为非右值引用限定的非静态成员函数,
  • ,若定义协程为右值引用限定的非静态成员函数。

例如:

  • 若定义协程为 ,则其 类型为 。
  • 若定义协程为 ,则其 类型为 。
  • 若定义协程为 ,则其 类型为 。

一元运算符 暂停协程并将控制返回给调用方。其操作数是一个表达式,其类型必须要么定义 ,要么能以当前协程的 转换到这种类型。

首先,以下列方式将 转换成可等待体(awaitable):

  • 若 由初始暂停点、最终暂停点或 yield 表达式所产生,则可等待体为 本身。
  • 否则,若当前协程的 Promise 类型拥有成员函数 ,则可等待体为 。
  • 否则,可等待体为 本身。

然后以下列方式获得等待器(awaiter)对象:

  • 若针对 的重载决议给出单个最佳重载,则等待器是该调用的结果(对于成员重载为 ,对于非成员重载为 )
  • 否则,若重载决议找不到 ,则等待器是可等待体本身
  • 否则,若重载决议有歧义,则程序非良构

若上述表达式为纯右值,则等待器对象是从它实质化的临时量。否则,若上述表达式为泛左值,则等待器对象是其所指代的对象。

然后,调用 (这是当已知结果就绪或可以同步完成时,用以避免暂停开销的快捷方式)。若其结果按语境转换成 为 ,则

暂停协程(以各局部变量和当前暂停点填充其协程状态)。
调用 ,其中 handle 是表示当前协程的协程句柄。这个函数内部可通过这个句柄观察暂停的协程,而且此函数负责调度它以在某个执行器上恢复,或将其销毁(并返回 当做调度)
  • 若 返回 ,则将控制立即返回给当前协程的调用方/恢复方(此协程保持暂停),否则
  • 若 返回 ,则
  • 值为 时将控制返回给当前协程的调用方/恢复方
  • 值为 时恢复当前协程。
  • 若 返回某个其他协程的协程句柄,则(通过调用 )恢复该句柄(注意这可以连锁进行,并最终导致当前协程恢复)
  • 若 抛异常,则捕捉该异常,恢复协程,并立即重抛异常
最后,调用 ,而其结果就是整个 表达式的结果。

若协程在 co_await 表达式中暂停,而之后恢复,则恢复点处于紧接对 的调用之前。

注意,因为协程在进入 前已完全暂停,所以该函数可以自由地在线程间转移协程柄,而无需额外同步。例如,可以将它放入回调,将它调度成在异步 I/O 操作完成时在线程池上运行等。该情况下,因为当前协程可能已被恢复,从而执行了等待器的析构函数,同时由于 在当前线程上持续执行, 应该把 当作已被销毁并且在柄被发布到其他线程后不再访问它。

注意:等待器对象是协程状态的一部分(作为生存期跨过暂停点的临时量),并且在 co_await 表达式结束前销毁。可以用它维护某些异步 I/O API 所要求的每操作内状态,而无需用到额外的堆分配。

标准库定义了两个平凡的可等待体: 及 。

yield 表达式向调用方返回一个值并暂停当前协程:它是可恢复生成器函数的常用构建块

它等价于

典型的生成器的 会将其实参存储(复制/移动或仅存储其地址,因为实参的生存期跨过 co_await 内的暂停点)到生成器对象中并返回 ,将控制转移给调用方/恢复方。

协程支持库定义数个类型,提供协程的编译与运行时支持。

版权声明


相关文章:

  • jconsole远程连接tomcat2025-08-19 08:01:03
  • 小驼峰命名有哪些2025-08-19 08:01:03
  • 面向对象设计图2025-08-19 08:01:03
  • nlp技巧是什么2025-08-19 08:01:03
  • py pyc文件2025-08-19 08:01:03
  • 学windows内核有啥用2025-08-19 08:01:03
  • io的含义2025-08-19 08:01:03
  • edge网站不安全不让进怎么办2025-08-19 08:01:03
  • kvm虚拟化有哪些组件组成2025-08-19 08:01:03
  • 交叉编译教程2025-08-19 08:01:03