c++20 协程本质

2023-05-16

c++20 协程本质

背景:

最近因项目关系,web端,js异步调用,发现跟本门的C++20 还是有些不一样的,本文主要从另外一个角度来看

什么是协程

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

腾讯用c语言实现了一个有栈携程,libco,有兴趣的同学可以看下,跟本文讲的协程不太一样。

当一个函数中出现 co_yeild, co_wait, co_return,它就是一个协程

了解一些概念

承诺(promise)对象,从协程内部操纵。协程通过此对象提交其结果或异常。

协程句柄 (coroutine handle),从协程外部操纵。这是用于恢复协程执行或销毁协程帧的非拥有柄

co_yeild, co_wait, co_return ,可以参照https://zh.cppreference.com/w/cpp/language/coroutines里边自行看下,

我们主要分析协程在编译后会变成什么样子

示例代码

C++
#include <coroutine>
#include <exception>
#include <iostream>
#include <thread>

struct Generator {

  class ExhaustedException : std::exception {};

  struct promise_type {
    int value;
    bool is_ready = false;

    std::suspend_always initial_suspend() { return {}; };

    std::suspend_always final_suspend() noexcept { return {}; }

    std::suspend_always yield_value(int value) {
      this->value = value;
      is_ready = true;
      return {};
    }

    void unhandled_exception() {

    }

    Generator get_return_object() {
      return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
    }

    void return_void() {}
  };

  std::coroutine_handle<promise_type> handle;

  bool has_next() {
    if (handle.done()) {
      return false;
    }

    if (!handle.promise().is_ready) {
      handle.resume();
    }

    if (handle.done()) {
      return false;
    } else {
      return true;
    }
  }

  int next() {
    if (has_next()) {
      handle.promise().is_ready = false;
      return handle.promise().value;
    }

  }

  explicit Generator(std::coroutine_handle<promise_type> handle) noexcept
      : handle(handle) {}

 

  ~Generator() {
    if (handle) handle.destroy();
  }
};

Generator fibonacci() {
  co_yield 0;
  co_yield 1;

  int a = 0;
  int b = 1;
  while (true) {
    co_yield a + b;
    b = a + b;
    a = b - a;
  }
}

int main() {
  auto generator = fibonacci();
  for (int i = 0; i < 10; ++i) {
    if (generator.has_next()) {
      std::cout << generator.next() << " " << std::endl;
    } else {
      break;
    }
  }
  return 0;
}

原示例很简单,斐波那契数列,考虑以下问题:

上文中的fibonacci方法中,int a; int b 存在哪里,是栈空间还是堆空间

为什么fibonacci方法返回的Generator里边会定义struct promise_type 内部结构体

协程的帧到底长什么样子的

协程挂起与恢复的本质是什么

带着上边疑问,接下来就开始分析下,编译器把这块代码转成什么样子的

C++

struct __fibonacciFrame
{
  void (*resume_fn)(__fibonacciFrame *);
  void (*destroy_fn)(__fibonacciFrame *);
  std::__coroutine_traits_impl<Generator>::promise_type __promise;
  int __suspend_index;
  bool __initial_await_suspend_called;
  int a;
  int b;
  std::suspend_always __suspend_71_11;
  std::suspend_always __suspend_72_3;
  std::suspend_always __suspend_73_3;
  std::suspend_always __suspend_78_5;
  std::suspend_always __suspend_71_11_1;
};

Generator fibonacci()
{
  /* Allocate the frame including the promise */
  __fibonacciFrame * __f = reinterpret_cast<__fibonacciFrame *>(operator new(__builtin_coro_size()));
  __f->__suspend_index = 0;
  __f->__initial_await_suspend_called = false;
  
  /* Construct the promise. */
  new (&__f->__promise)std::__coroutine_traits_impl<Generator>::promise_type{};
  
  /* Forward declare the resume and destroy function. */
  void __fibonacciResume(__fibonacciFrame * __f);
  void __fibonacciDestroy(__fibonacciFrame * __f);
  
  /* Assign the resume and destroy function pointers. */
  __f->resume_fn = &__fibonacciResume;
  __f->destroy_fn = &__fibonacciDestroy;
  
  /* Call the made up function with the coroutine body for initial suspend.
     This function will be called subsequently by coroutine_handle<>::resume()
     which calls __builtin_coro_resume(__handle_) */
  __fibonacciResume(__f);
  
  
  return __promise.get_return_object();
}


/* This function invoked by coroutine_handle<>::destroy() */
void __fibonacciDestroy(__fibonacciFrame * __f)
{
  /* destroy all variables with dtors */
  __f->~__fibonacciFrame();
  /* Deallocating the coroutine frame */
  operator delete(__builtin_coro_free(static_cast<void *>(__f)));
}
 

方便分析,上边代码已经删除了一部分,首先我们先回答第一个问题,这两个变量存放位置,

struct __fibonacciFrame 里边有int a; int b;两个成员,在fibonacci(),可以看到使用了new 关键字分配对象,也就是说int a;int b;被编译器放到堆里边了,

第二个问题:

为什么要定义struct promise_type,通过上边的代码也可以看出来, new (&__f->__promise) std::__coroutine_traits_impl<Generator>::promise_type{}; 萃取机已经明确要求,里边要含有promise_type, 并且这个里边要有明确的几个协程必备的方法定义。另外通过__promise.get_return_object()可以构建Generator 对象,

第三个问题:

协程帧的样子就是struct __fibonacciFrame这个里边定义的,包含协程恢复与销毁的方法

第四个问题:

协程挂起与恢复,本质上讲就是函数调用,可以看下下边代码,协程恢复本质上就是标记一个值,每次调用的时候根据这个值,goto 到指定的代码位置,协程里边局部成员都保存在堆里边了,可以直接使用,

C++
/* This function invoked by coroutine_handle<>::resume() */
void __fibonacciResume(__fibonacciFrame * __f)
{
  try
  {
    /* Create a switch to get to the correct resume point */
    switch(__f->__suspend_index) {
      case 0: break;
      case 1: goto __resume_fibonacci_1;
      case 2: goto __resume_fibonacci_2;
      case 3: goto __resume_fibonacci_3;
      case 4: goto __resume_fibonacci_4;
    }
    
    /* co_await insights.cpp:71 */
    __f->__suspend_71_11 = __f->__promise.initial_suspend();
    if(!__f->__suspend_71_11.await_ready()) {
      __f->__suspend_71_11.await_suspend(std::coroutine_handle<Generator::promise_type>::from_address(static_cast<void *>(__f)).operator coroutine_handle());
      __f->__suspend_index = 1;
      __f->__initial_await_suspend_called = true;
      return;
    }
    
    __resume_fibonacci_1:
    __f->__suspend_71_11.await_resume();
    
    /* co_yield insights.cpp:72 */
    __f->__suspend_72_3 = __f->__promise.yield_value(0);
    if(!__f->__suspend_72_3.await_ready()) {
      __f->__suspend_72_3.await_suspend(std::coroutine_handle<Generator::promise_type>::from_address(static_cast<void *>(__f)).operator coroutine_handle());
      __f->__suspend_index = 2;
      return;
    }
    
    __resume_fibonacci_2:
    __f->__suspend_72_3.await_resume();
    
    /* co_yield insights.cpp:73 */
    __f->__suspend_73_3 = __f->__promise.yield_value(1);
    if(!__f->__suspend_73_3.await_ready()) {
      __f->__suspend_73_3.await_suspend(std::coroutine_handle<Generator::promise_type>::from_address(static_cast<void *>(__f)).operator coroutine_handle());
      __f->__suspend_index = 3;
      return;
    }
    
    __resume_fibonacci_3:
    __f->__suspend_73_3.await_resume();
    __f->a = 0;
    __f->b = 1;
    while(true) {
      
      /* co_yield insights.cpp:78 */
      __f->__suspend_78_5 = __f->__promise.yield_value(__f->a + __f->b);
      if(!__f->__suspend_78_5.await_ready()) {
        __f->__suspend_78_5.await_suspend(std::coroutine_handle<Generator::promise_type>::from_address(static_cast<void *>(__f)).operator coroutine_handle());
        __f->__suspend_index = 4;
        return;
      }
      
      __resume_fibonacci_4:
      __f->__suspend_78_5.await_resume();
      __f->b = (__f->a + __f->b);
      __f->a = (__f->b - __f->a);
    }
    
    goto __final_suspend;
  } catch(...) {
    if(!__f->__initial_await_suspend_called) {
      throw ;
    }
    
    __f->__promise.unhandled_exception();
  }
  
  __final_suspend:
  
  /* co_await insights.cpp:71 */
  __f->__suspend_71_11_1 = __f->__promise.final_suspend();
  if(!__f->__suspend_71_11_1.await_ready()) {
    __f->__suspend_71_11_1.await_suspend(std::coroutine_handle<Generator::promise_type>::from_address(static_cast<void *>(__f)).operator coroutine_handle());
  }
  
  ;
}

协程恢复就更好理解了,

C++
if(!__f->__suspend_73_3.await_ready()) {
      __f->__suspend_73_3.await_suspend(std::coroutine_handle<Generator::promise_type>::from_address(static_cast<void *>(__f)).operator coroutine_handle());
      __f->__suspend_index = 3;
      return;
    }

await_ready return false,就直接进入里边,然后就return了,很好理解

总结:

通过上边的理解,我们可以看到协程本质上还是函数调用,利用goto语句来实现协程挂起与恢复

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++20 协程本质 的相关文章

  • python调用IP摄像头

    利用RTSP 43 opencv就可以实现网络摄像头的调用 rtsp是实时流传输协议 xff0c 是基于TCP IP协议体系中的一个应用层协议 xff0c 可以控制声音或者影像的多媒体串流协议 但是不同品牌的摄像头有不同的RTSP地址 下面
  • 22岁-时光如河,浮生为鱼

    时光如河 xff0c 浮生为 x1f41f 还没有学会告别 xff0c 四年就这样悄悄过去了 如往年今日一样 xff0c 依旧写些懒懒散散的文字致敬这一年的时光 x1f495 22岁生日快乐 x1f495 全文约4200字 xff0c 阅读
  • 电子书下载网站汇总

    网站名称地址简介语言推荐指数备注Book4Uhttp www book4you sk 外文下载网站斯洛伐克语 BookYardshttps www bookyards com en welcome主要面向教师的门户网站 xff0c 其中的书
  • Docker版 E5续订的E5调用API续订服务:Microsoft 365 E5 Renew X

    本文是基于作者SundayRX提出的E5 调用API续订服务 xff1a Microsoft 365 E5 Renew X的基础上提出的Docker版本的E5调用API续订服务 基础的账号注册等过程见SundayRX的博客 xff1a 账号
  • Docker版 Linux百度网盘备份工具

    一些必须要知道的事 xff1a 这个镜像的主要目的是为了将服务器或者群晖等linux场景中的资料备份到百度网盘中容器的基础镜像为ubuntu镜像 容器的备份周期为每天的凌晨两点 具体步骤如下 xff1a 下载镜像 docker pull h
  • 操作系统(五)中断和异常

    1 5 中断和异常 在上节内核态与用户态的转换过程中曾经提到过 xff0c 操作系统会响应中断信号强制夺回CPU使用权 xff0c 使用户态转换为内核态 中断 是操作系统夺回CPU使用权的唯一方式 xff0c 如果没有 中断 机制 xff0
  • Mediacodec 如何硬件解码到纹理的

    Mediacodec 如何硬件解码到纹理的 背景 xff1a 网上很多关于mediacodec xff0c surface xff0c surfacetexture的源码分析 xff0c 以及内部原理 xff0c 但是都局限于各自的内容 x
  • pyinstaller 递归深度设置(A RecursionError occurred)

    简介 xff1a pyinstaller常用于打包python文件 xff0c 当导入的包过多时常会出现一个递归深度超过限制的错误 这个可以通过设置最大递归深度来解决 1 pyinstaller报错信息 61 61 61 61 61 61
  • labelme标注格式转coco格式

    摘要 xff1a labelme是广泛使用的深度学习标注工具 xff0c 支持目标检测和实例分割等任务的标注 xff0c 但是一些框架如detectron2 xff0c solo等需要的是coco格式的 xff0c 这里提供一个示例把lab
  • opencv C++ 旋转任意角度图片

    摘要 xff1a opencv里面似乎没有直接的旋转图片的接口 xff0c 这里实现一个旋转任意角度的方法 xff0c 在旋转的时候调用opencv里面的仿射变换函数实现 有两种旋转模式 xff1a 一种按图片中心旋转 xff0c 尺寸与原
  • C++ opencv曲线拟合

    简介 xff1a 此问题是在做旋转模板匹配的时候 xff0c 选择最好的匹配结果时产生的 查找资料发现多项式拟合问题可以变成一个超定方程的求解问题 xff0c 而opencv中本身有一个cv solve 函数可以求解线性方程组 xff0c
  • C# 中的Bitmap 和(c++)opencv之间的传递

    C 中的Bitmap 和 xff08 c 43 43 xff09 opencv之间的传递 文章目录 C 中的Bitmap 和 xff08 c 43 43 xff09 opencv之间的传递1 C 传递bitmap给C 43 43 2 Pix
  • opencv kmeans (C++)

    kmeans 函数原型 span class token keyword double span cv span class token operator span span class token function kmeans span
  • C++ explict

    文章目录 C 43 43 中的explicit是一个关键字 xff0c 用于修饰构造函数 xff0c 它可以防止编译器进行隐式类型转换 xff0c 只允许显式地调用构造函数 它不能用于隐式转换和复制初始化 例如 xff0c 在下面的代码中
  • C++ friend

    在C 43 43 中 xff0c friend是一个关键字 xff0c 用于声明一个非成员函数或类可以访问另一个类的私有成员 例如 xff0c 我们有一个名为ClassA的类 xff1a span class token keyword c
  • C++ enum 和enum class

    文章目录 C 43 43 enum 和 enum class共同点区别 C 43 43 enum 和 enum class 在C 43 43 中 xff0c enum 是一种定义枚举类型的方法 一个枚举是一个整数值的命名集合 可以通过以下方
  • VS2019设置cl.exe环境变量

    版本 xff1a VS2019 设置 cl 环境变量 xff08 加入系统环境变量 xff08 PATH xff09 xff09 步骤 xff1a 1 找到cl exe的所在路径 xff0c 一般都在 xff1a C Program Fil
  • 从汇编角度看c++20 协程

    从汇编角度看c 43 43 20 协程 背景 xff1a 在学习c 43 43 20 协程的时候 xff0c 总对协程里边的局部成员存储 xff0c 以及协程栈恢复有很多疑问 xff0c 本次从过年arm64角度来分析下 xff0c 具体情
  • Win10 使用 Xrdp 远程控制 ubuntu16 闪退

    问题描述 win10使用 Xrdp 远程控制 ubuntu16 4 出现闪退 都能到这一步 xff0c 但是刚登上Xrdp4桌面就闪退 xff0c 回到下图 xfce4桌面xubuntu desktop xff0c 重新建立桌面会话 spa
  • 【华为OD机试真题 Java】字符串重新排列

    前言 本专栏将持续更新华为OD机试题目 并进行详细的分析与解答 包含完整的代码实现 希望可以帮助到正在努力的你 关于OD机试流程 面经 面试指导等 如有任何疑问 欢迎联系我 wechat steven moda email nansun09

随机推荐

  • atcoder abc140E (计算贡献)

    题目链接 大意 xff1a 让你 i 61 1 n
  • AtCoder Beginner Contest 143 E.Travel by Car(最短路)

    题目链接 大意 xff1a 给你一个无向带权图 xff0c 给你一些询问点 xff0c s t s t s t xff0c 你从s出发有 l
  • Ubuntu20.04软件安装大全

    目录 Ubuntu20 04 软件安装大全前言1 Windows和Ubuntu双系统安装1 1 下载Ubuntu系统镜像1 2 磁盘分区1 3 GPT分区安装Ubuntu1 4 系统完成后的一些设置1 5 遇到的一些小bug 2 换源2 1
  • 基于Nginx构建七牛云CDN静态资源加速

    创建七牛云账号 七牛云 进入管理控制台创建对象存储 3 配置nginx 使用nginx rewrite 的重定向功能进行转发到七牛云 server listen 80 server name test com 你的域名 location g
  • ChinaSkills-网络系统管理(2021年全国职业院校技能大赛A-1 模块 A:Linux 环境 真题 )

    前言 随着近年国家对技术性的比赛越来越重视 xff0c 各类技能大赛举办的相较正规 xff0c 这类大赛一般是在专科中专等技术性高中等院校中选拔优秀人才 xff0c 并且技能大赛的一等奖选手将有资格保送本科 xff0c 希望一些有能力的专科
  • Cannot resolve symbol 解决方案汇总(6种解决方案)

    Cannot resolve symbol 39 xxx 39 是比较常见一种错误 xff0c 以下整理常见的六种解决方案 xff0c 第六种会说明一下造成的原因和如何避免 方案一 检查一下pom文件依赖是否正常 xff0c 如不正常刷新m
  • 踩坑指南!import cv2出错怎么办?

    好久没有更新 xff0c 最近代码相关问题看的比较少 xff0c 有时候忙着debug就忘记了记录 xff0c 反思一下 背景 xff1a 在提取视频帧序列的时候用到了opencv包 xff0c 结果运行出错 解决 xff1a 经过查找资料
  • 最小生成树——北极通讯网络

    问题 B 北极通讯网络 时间限制 1 Sec 内存限制 128 MB 提交 17 解决 7 提交 状态 讨论版 命题人 add xiezhenghao 题目描述 北极的某区域共有n座村庄 xff08 1 n 500 xff09 xff0c
  • c++ 判定子类是否重写父类虚方法

    背景 xff1a 在做业务的时候 xff0c 有时会蹦到一个业务逻辑 xff0c 如果子类重写父类方法 xff0c 就调用 xff0c 如果没重写就不调用它 xff0c 这个逻辑在大量子类集成同一个父类的 xff0c 会节约一点点性能 xf
  • Windows | RDPWrap 远程桌面登录增强工具 (解决win10/11家庭版无法使用远程桌面 + 支持多人同时登录)

    一 前言 Windows远程桌面 Windows远程桌面是一种技术 xff0c 允许用户从远程位置访问和控制在另一个地方的Windows计算机 它可以帮助管理员和其他用户实现远程管理 技术支持和协同工作等操作 使用Windows远程桌面 x
  • 字符串匹配(java)

    字符串匹配 字符串匹配可以用到蛮力法 对于字符串s和t xff0c 若t是s的子串 xff0c 返回t在s中的位置 xff08 t的首字符在s中的下标 xff09 xff0c 否则返回 1 采用的是穷举法 xff0c 从s的第一个字符开始查
  • 警告:Establishing SSL connection without server's identity verification is not recommended

    解决方法 那问题来了 xff0c SSL是什么 xff1f SSL xff08 Secure Socket Layer xff1a 安全套接字层 xff09 利用数据加密 身份验证和消息完整性验证机制 xff0c 为基于TCP等可靠连接的应
  • NestJs-项目创建

    NestJs Nest js 用于构建高效且可伸缩的服务端应用程序的渐进式 Node js 框架 项目创建 构建工具 可以使用 npm yarn pnpm进行包管理 xff0c 但议使用pnpm建议安装nrm镜像源管理工具 xff0c 可以
  • C++ 字符串格式化

    使用snprintf格式化字符串使用boost format格式化字符串使用stringstream格式化字符串 具体示例 使用snprintf格式化字符串 span class token macro property span clas
  • 玩客云 一个百元级的微型服务器

    前言 下面这段基本是copy的 xff0c 就是图个完整 xff0c 不要觉得奇怪哈 玩客云是一款前些年很火的矿机 xff0c 曾经在官网售卖 xffe5 599 xff0c 现在已经沦落到 xffe5 45包邮的田地了 当然这边一般有两种
  • OpenStack双节点部署—M Manila(共享文件系统服务)

    Manila安装 一 数据库配置二 创建服务凭证和API端点三 安装并配置Heat四 启动服务并设置开机自启 一 数据库配置 Controller节点 mysql span class token operator span uroot s
  • Dockerfile简介

    1 什么是dockerfile Dockerfile是一个包含用于组合映像的命令的文本文档 可以使用在命令行中调用任何命令 Docker通过读取Dockerfile中的指令自动生成映像 docker build命令用于从Dockerfile
  • 论文阅读笔记1:EKT: Exercise-aware Knowledge Tracing for Student Performance Prediction

    该篇论文于2019年在IEEE发表 xff0c 作者为 xff1a Qi Liu Zhenya Huang Yu Yin Enhong Chen Hui Xiong Yu Su and Guoping Hu 等 知识追踪 xff08 Kno
  • 树莓派 同时使用有线和无线网卡

    树莓派同时使用有线和无线 使能双网卡待机 添加路由使用规范 备注 192 168 1 0 表明所有以192 168 1开始的网段 都将从后面的设备发送相应的数据 使能双网卡待机 Linux通过设置默认的网络信息实现双网卡待机 设置方法如下
  • c++20 协程本质

    c 43 43 20 协程本质 背景 xff1a 最近因项目关系 xff0c web端 xff0c js异步调用 xff0c 发现跟本门的C 43 43 20 还是有些不一样的 xff0c 本文主要从另外一个角度来看 什么是协程 协程是能暂