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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

测试代码

#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的那一行。
只要被挂起就会从当前协程函数中跳出继续从"切入点"的下一行开始执行。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++