飞道的博客

从运行角度解读c++20 协程

232人阅读  评论(0)

测试代码

#include <coroutine>
#include <iostream>

/**
 * @brief 协程特定返回类型
 */
struct task {
   
  struct promise_type {
   
    std::suspend_never initial_suspend() noexcept {
   
      std::cout << " 4, initial_suspend " << std::endl << std::endl;
      return {
   };
    }

    std::suspend_never final_suspend() noexcept {
   
      std::cout << "20, final_suspend " << std::endl;
      return {
   };
    }

    void unhandled_exception() noexcept {
   }

    task get_return_object() noexcept {
   
      std::cout << " 2, get_return_object " << std::endl;
      auto tk = task{
   std::coroutine_handle<promise_type>::from_promise(*this)};
      return tk;
    }

    void return_value(int v) noexcept {
   
      std::cout << "19, return_value: " << v << std::endl << std::endl;
    }

    std::suspend_always yield_value(std::string&& from) noexcept {
   
      std::cout << "14, yield_value: " << from << std::endl;
      value_ = std::move(from);
      return {
   };
    }
    std::string value_;
  };

  std::coroutine_handle<promise_type> coro_;
  task(std::coroutine_handle<promise_type> h) : coro_(h) {
   
    std::cout << " 3, task construct " << std::endl;
  }

  ~task() {
    std::cout << "22, task deconstruct " << std::endl; }

  std::string value() {
    return coro_.promise().value_; }
};

/**
 * @brief 协程 co_await关键字需要的awaitable
 */
struct awaiter {
   
  awaiter() {
    std::cout << " 6, awaiter construct" << std::endl; }

  ~awaiter() {
    std::cout << "11, awaiter deconstruct" << std::endl; }

  bool await_ready() noexcept {
   
    std::cout << " 7, await_ready " << std::endl;
    return false;
  }

  void await_resume() noexcept {
   
    std::cout << "10, await_resume " << std::endl;
  }

  void await_suspend(std::coroutine_handle<>) noexcept {
   
    std::cout << " 8, await_suspend " << std::endl;
  }
};

/**
 * @brief 协程函数
 * @return 协程特定返回类型
 */
task do_by_coroutine() {
   
  std::cout << " 5, before co_await " << std::endl;
  co_await awaiter{
   };
  std::cout << "12, after co_await " << std::endl << std::endl;

  std::cout << "13, before co_yield " << std::endl;
  co_yield "123";
  std::cout << "17, after co_yield " << std::endl << std::endl;

  std::cout << "18, before co_return " << std::endl;
  co_return 3;
}

int main() {
   
  std::cout << " 1, main start" << std::endl;
  auto result = do_by_coroutine();
  std::cout << " 9, resume co_await-suspend " << std::endl;
  result.coro_.resume();

  std::cout << "15, get yield_value: " << result.value() << std::endl;
  std::cout << "16, resume co_yield-suspend " << std::endl;
  result.coro_.resume();

  std::cout << "21, main end" << std::endl;
  return 0;
}

 

运行输出

 1, main start
 2, get_return_object
 3, task construct
 4, initial_suspend

 5, before co_await
 6, awaiter construct
 7, await_ready
 8, await_suspend
 9, resume co_await-suspend
10, await_resume
11, awaiter deconstruct
12, after co_await

13, before co_yield
14, yield_value: 123
15, get yield_value: 123
16, resume co_yield-suspend
17, after co_yield

18, before co_return
19, return_value: 3

20, final_suspend
21, main end
22, task deconstruct

 

运行输出解读

第一块 1~4

调用协程函数do_by_coroutine,协程进入准备阶段。
先调用get_return_object构造协程特定返回对象,在函数get_return_object中构造并返回协程特定返回对象task。
紧接着调用initial_suspend初始化协程状态,文中为不挂起。
到此协程准备结束。

第二块 5~12

本块 属于最讲究的co_await操作符使用。
开始协程do_by_coroutine执行,先构造出awaiter对象,接着判断await_ready状态,文中是false,属于未ready状态,所以紧接着调用await_suspend,协程被挂起。
因为协程被挂起,所以从协程函数do_by_coroutine中跳出,往下执行以下语句:

std::cout << " 9, resume co_await-suspend " << std::endl;
result.coro_.resume();

然后协程被恢复,所以又跳回协程函数do_by_coroutine中,对应的await_resume函数被执行。
到此co_await awaiter{} 这一行 执行结束,awaiter对象被析构。打印出 “12, after co_await”。

第三块 13~17

本块 属于较为讲究的co_yield操作符使用。
目前还在协程函数do_by_coroutine中,所以接着执行co_yield “123” ,对应的yield_value函数被执行,同时yield_value形参from被设置为“123”。上篇文中提到co_yield是co_await的变体,且文中yield_value返回的是suspend_always,所以协程又一次被挂起。
因为协程被挂起,所以从协程函数do_by_coroutine中跳出,从第一句 result.coro_.resume() 处接着往下执行:

std::cout << "15, get yield_value: " << result.value() << std::endl;
std::cout << "16, resume co_yield-suspend " << std::endl;
result.coro_.resume();

然后协程被恢复,所以又跳回协程函数do_by_coroutine中,到此co_yield “123” 这一行 执行结束,打印出 “17, after co_yield”。

第四块 18~19

本块 属于最简单的co_return操作符使用。
目前还在协程函数do_by_coroutine中,所以co_return 3 被执行,对应的return_value函数被执行。协程函数执行结束。

第五块 20~22

因为第四块中的co_return操作符,所以协程执行结束。本块属于协程结束收尾阶段,final_suspend函数首先被调用。
main函数结束。
特定返回类型task的result对象被析构。
程序退出。

协程跳转

协程最让人难以理解的就是“跳来跳去”。其实理解起来也不难。
遇到co_return就表示协程结束,不会再有“跳来跳去”。
遇到co_yield或者co_await,需要看awaitable是 suspend_always还是suspend_never。不过协程通常会被挂起
只要被挂起,就需要调用resume才能回到协程函数的“切出点”(调用co_yield或者co_await的那一行)。
只要被挂起,就会从当前协程函数中跳出,继续从"切入点"的下一行开始执行。


转载:https://blog.csdn.net/qq_15328161/article/details/128554203
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场