c++20 协程本质
创始人
2024-05-30 06:10:26
0

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
#include
#include
#include

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::from_promise(*this)};
    }

    void return_void() {}
  };

  std::coroutine_handle 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 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::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::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(__f)));
}
 

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

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

第二个问题:

为什么要定义struct promise_type,通过上边的代码也可以看出来, new (&__f->__promise) std::__coroutine_traits_impl::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::from_address(static_cast(__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::from_address(static_cast(__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::from_address(static_cast(__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::from_address(static_cast(__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::from_address(static_cast(__f)).operator coroutine_handle());
  }
  
  ;
}

协程恢复就更好理解了,

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

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

总结:

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

相关内容

热门资讯

清朝有很多有名的历史人物,能给... 清朝有很多有名的历史人物,能给大家说几个清朝的历史人物吗?林则徐,张之洞,乾隆皇帝,曾国藩,朱耷,这...
曹丕的“太子四友”指的是谁 曹丕的“太子四友”指的是谁首先说,曹丕这四个,陈群司马懿是顶级的谋士和政治家,吴质有些小聪明,朱铄不...
在这次遇难者中存在了几名幸存者... 在这次遇难者中存在了几名幸存者。这句话是不是逻辑错误这句话的逻辑没有问题,有问题的是用词不当。遇难者...
在中国古代,有许多充满哲学智慧... 在中国古代,有许多充满哲学智慧的成语典故、寓言故事,如...在中国古代,有许多充满哲学智慧的成语典故...
清澈的意思是什么,… 清澈的意思是什么,…清净而明澈清而透明【造句】看着他清澈而又天真的眼眸,我的心久久不能平静……
蚂蚁森林合种爱情树一方退出怎么... 蚂蚁森林合种爱情树一方退出怎么找回来两个人合种的爱情树,我退出来,我怎么再次加入进去继续合作那个树?...
有好看的古代修炼小说推荐吗? 有好看的古代修炼小说推荐吗?古代重生穿越修炼......让我帮你找一下这些古代修炼的小说,找到这些类...
女主重生爱上前世辜负的人 女主重生爱上前世辜负的人重生我是你正妻渣女重生之竹马重生之弃渣重生之夫君可欺重生之换我疼你重生妇归来...
华胥引有广播剧吗 华胥引有广播剧吗现有的华胥引的广播剧是忆语广播剧社出品的,只有十三月和杯(这个是错字,请无视,居然输...
选文韩麦尔先生在说了,我的朋友... 选文韩麦尔先生在说了,我的朋友们我就要离开你们呢了,再见了银头鲑鱼tj75rt6yturdrruv ...
中通快递从北京保定市到广东揭阳... 中通快递从北京保定市到广东揭阳普宁要多久?中通快递从北京保定市到广东揭阳普宁要多久?从北京到广东需要...
关于离婚悲伤的歌曲 关于离婚悲伤的歌曲关于离婚悲伤的歌曲林俊杰《可惜没如果》 张靓颖《我走以后》 金志文《流着泪说分手》...
形容文笔差怎么说啊? 形容文笔差怎么说啊?哥哥姐姐,麻烦问下,我是做文员的,形容文案方面的工作很差应该怎么说啊?粗鄙怎么样...
我想做未婚妈妈,可行吗? 我想做未婚妈妈,可行吗?没关系吧?我同学好多他们妈妈都是30岁以后省得他们,都没事啊,但是如果你自己...
如何在两个excel表格里筛选... 如何在两个excel表格里筛选出重复的名字如何在两个excel表格里筛选出重复的名字1、电脑打开EX...
且试天下 哪些小说是用白绫做武... 且试天下 哪些小说是用白绫做武器的?神雕侠侣吖- -..小龙女一开始就是用白绫的聊斋 辛十四娘嘿嘿~...
自带高冷体质,笑起来温暖又治愈... 自带高冷体质,笑起来温暖又治愈的星座,你了解吗?虽然天生高冷体质,但是笑起来特别的温暖治愈的新作用天...
火星未解之谜 火星未解之谜多列举一些,每个事例最好长一点,谢啦~“火星人脸”, “地表被水冲击河道”,“原始大气和...
我是1991年10月4号生的,... 我是1991年10月4号生的,是什么星座啊有的说是处女座有的说是天平座,糊涂了,到底是什么啊很负责任...
梦见白狐狸,然后当时我骑着自行... 梦见白狐狸,然后当时我骑着自行车,我想躲开它,他很凶的的追赶我,最后它向我扑了过来,然后我就醒乐.你...