C++ 20 协程 Coroutine之剖析

开发 前端
C++20协程在启动前,开始会new 一个协程状态(coroutine state​)。然后构造协程的承诺对象(promise​)。承诺对象(promise​)通过get_return_object()​构造协程的返回值result​。

我们来剖析一下协程的过程。通过这个剖析,希望达到梳理协程几个重要概念的关系,把这些点串起来。所以在概念参考我们列出了相应的概念文字。

协程的创建

C++20协程在启动前,开始会new 一个协程状态(coroutine state​)。然后构造协程的承诺对象(promise​)。承诺对象(promise​)通过get_return_object()​构造协程的返回值result​。这个返回值在协程第一次挂起时,赋值给调用者。然后通过co_await promise.initial_suspend()​,决定协程初试完成后的行为。如果返回std::suspend_always​,初始化就挂起,如果返回std::suspend_never ,初始化后就继续运行。(注意initial_suspend也可以返回其他协程体)

图片

协程的co_await

cw_ret = co_await awaiter​ 或者cw_ret = co_await fun()​,先计算表达式fun,fun返回结果,就是一个等待体awaiter​。系统先调用awaiter.await_ready()​接口,看等待体是否准备好了,没准备好(return false​)就调用awaiter.await_suspend()。await_suspend​根据参数可以记录调用其的协程的的句柄。await_suspend​的返回值为return true​ ,或者 return void 就会挂起协程。

后面在外部如果恢复了协程的运行,awaiter.await_resume()​接口被调用。其返回结果,作为co_await的返回值。

图片

协程的co_yield

co_yield cy_ret;​,相当于调用co_wait promise.yield_value(cy_ret)​,你可以在yield_value​中记录参数cy_ret​后面使用,yield_value​的返回值如果是std::suspend_always​,协程挂起,如果返回std::suspend_never ,协程就继续运行。

图片

协程的co_return

co_yield cr_ret;​,调用promise.retun_value(cr_ret)​,如果没有返回值相当于promise.retun_viod()​,你可以在retun_value​中记录参数cr_ret​后面使用。然后调用co_await promise.final_suspend(void)​,如果返回值是std::suspend_always​,你需要自己手动青清理coroutine handle​,调用handle.destroy()。

图片

这儿存在一个疑问,final_suspend​,并没有真正挂起协程。看C++ 参考,里面说的也是calls promise.final_suspend() and co_awaits the result.​。按说如果返回应该要挂起。但用VS 2022测试是不会挂起的,再探 C++20 协程文章中说的是如果返回std::suspend_always​,需要你自己清理coroutine handle。存疑吧。

概念参考附录:

这些概念在原文第一章都有,附录在此仅供您方便参考。

协程状态(coroutine state)

协程状态(coroutine state)是协程启动开始时,new空间存放协程状态,协程状态记录协程函数的参数,协程的运行状态,变量。挂起时的断点。

注意,协程状态 (coroutine state​)并不是就是协程函数的返回值RET。虽然我们设计的RET一般里面也有promise和coroutine handle​,大家一般也是通过RET去操作协程的恢复,获取返回值。但coroutine state​理论上还应该包含协程运行参数,断点等信息。而协程状态 (coroutine state​)应该是协程句柄(coroutine handle)对应的一个数据,而由系统管理的。

承诺对象(promise)

承诺对象的表现形式必须是result::promise_type,result为协程函数的返回值。

承诺对象是一个实现若干接口,用于辅助协程,构造协程函数返回值;提交传递co_yield,co_return的返回值。明确协程启动阶段是否立即挂起;以及协程内部发生异常时的处理方式。其接口包括:

  • auto get_return_object() :用于生成协程函数的返回对象。
  • auto initial_suspend():用于明确初始化后,协程函数的执行行为,返回值为等待体(awaiter),用co_wait调用其返回值。返回值为std::suspend_always 表示协程启动后立即挂起(不执行第一行协程函数的代码),返回std::suspend_never 表示协程启动后不立即挂起。(当然既然是返回等待体,你可以自己在这儿选择进行什么等待操作)
  • void return_value(T v):调用co_return v后会调用这个函数,可以保存co_return的结果
  • auto yield_value(T v):调用co_yield后会调用这个函数,可以保存co_yield的结果,其返回其返回值为std::suspend_always表示协程会挂起,如果返回std::suspend_never表示不挂起。
  • auto final_suspend() noexcept:在协程退出是调用的接口,返回std::suspend_never ,自动销毁 coroutine state 对象。若 final_suspend 返回 std::suspend_always 则需要用户自行调用 handle.destroy() 进行销毁。但值得注意的是返回std::suspend_always并不会挂起协程。

前面我们提到在协程创建的时候,会new协程状态(coroutine state​)。你可以通过可以在 promise_type​ 中重载 operator new​ 和 operator delete,使用自己的内存分配接口。(请参考再探 C++20 协程)

协程句柄(coroutine handle)

协程句柄(coroutine handle)是一个协程的标示,用于操作协程恢复,销毁的句柄。

协程句柄的表现形式是std::coroutine_handle<promise_type>​,其模板参数为承诺对象(promise)类型。句柄有几个重要函数:

  • resume()函数可以恢复协程。
  • done()函数可以判断协程是否已经完成。返回false标示协程还没有完成,还在挂起。

协程句柄和承诺对象之间是可以相互转化的。

  • std::coroutine_handle<promise_type>::from_promise :这是一个静态函数,可以从承诺对象(promise)得到相应句柄。
  • std::coroutine_handle<promise_type>::promise() 函数可以从协程句柄coroutine handle得到对应的承诺对象(promise)

等待体(awaiter)

co_wait 关键字会调用一个等待体对象(awaiter)。这个对象内部也有3个接口。根据接口co_wait  决定进行什么操作。

  • bool await_ready():等待体是否准备好了,返回 false ,表示协程没有准备好,立即调用await_suspend。返回true,表示已经准备好了。
  • auto await_suspend(std::coroutine_handle<> handle)如果要挂起,调用的接口。其中handle参数就是调用等待体的协程,其返回值有3种可能

void 同返回true

bool 返回true 立即挂起,返回false 不挂起。

返回某个协程句柄(coroutine handle),立即恢复对应句柄的运行。

  • auto await_resume() :协程挂起后恢复时,调用的接口。返回值作为co_wait 操作的返回值。

等待体(awaiter)值得用更加详细的笔墨书写一章,我们就放一下,先了解其有2个特化类型。

  • std::suspend_never类,不挂起的的特化等待体类型。
  • std::suspend_always类,挂起的特化等待体类型。

前面不少接口已经用了这2个特化的类,同时也可以明白其实协程内部不少地方其实也在使用co_wait 关键字。

本章总结

此章讲解了协程的启动,3个关键字的细节。您可以通过这些关键概念,融合协程状态(coroutine state​),承诺对象(promise​),协程句柄(coroutine handle​),等待体(awaiter)。

参考文档

初探 C++20 协程

再探 C++20 协程

Coroutines (C++20)

协程(coroutine)简介

The Coroutine in C++ 20 协程之诺

C++ Coroutines: Understanding operator co_await

责任编辑:武晓燕 来源: 码砖杂役
相关推荐

2022-09-06 20:30:48

协程Context主线程

2022-09-10 18:51:09

C++协程主线程

2023-11-04 20:00:02

C++20协程

2013-12-12 16:44:25

Lua协程

2021-05-20 09:14:09

Kotlin协程挂起和恢复

2021-09-16 09:59:13

PythonJavaScript代码

2021-08-04 16:19:55

AndroidKotin协程Coroutines

2010-01-14 17:42:47

CC++

2010-01-28 16:31:54

C++类型

2023-11-17 11:36:59

协程纤程操作系统

2023-07-13 08:06:05

应用协程阻塞

2021-02-19 06:56:33

架构协程应用

2014-02-11 09:28:57

2022-07-18 15:32:37

C++虚函数表

2023-10-24 19:37:34

协程Java

2021-12-09 06:41:56

Python协程多并发

2010-02-06 16:05:51

C++ Vector

2021-05-21 08:21:57

Go语言基础技术

2010-01-15 10:32:21

C++语言

2010-02-04 10:19:39

C++多线程
点赞
收藏

51CTO技术栈公众号