C++20中的协程(Coroutine)功能解析:co_await

协程的概念

C++20引入了协程(coroutine)的概念,首先介绍下什么是协程: 协程是一个可以暂停执行以便稍后恢复的函数。但与函数不同的是协程是无堆栈的:它们通过返回调用者(caller)来暂停执行,恢复执行所需的数据与堆栈分开存储。这允许异步执行顺序代码(例如,在没有显式回调的情况下处理非阻塞I/O),并且还支持延迟计算无限序列和其他用途的算法。

也就是说,通常一个函数的函数体是顺序执行的,只有执行完才会将结果返回给调用者,而对于无栈的协程,可以把函数挂起,然后在任意需要的时刻恢复并执行函数体。

协程的主要优点

协程可以说是进程和线程的升级版,进程和线程切换都会有内核态和用户态的切换,而协程则是用户自己控制切换的时机,并不需要切换至内核态,所以这样以来执行效率非常高。因为程序的切换没有线程切换的开销,并且由于只有一个线程,也不存在同时读写变量的冲突,在协程中控制共享资源不需要加锁,只需要判断数据的状态,故执行效率远高于线程。特别是针对多核的情况,可以使用多进程+协程的方式尽可能提高效率。

协程的使用

一个函数如果想成为一个协程,可通过使用co_return,co_await或者co_yield这三个关键自来实现。

官方实例

官方实例,要求g++版本>=10.0,需要支持C++20。

​

#include <coroutine>
#include <iostream>
 
struct promise;
 
struct coroutine : std::coroutine_handle<promise>
{
    using promise_type = ::promise;
};
 
struct promise
{
    coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
};
 
struct S
{
    int i;
    coroutine f()
    {
        std::cout << i;
        co_return;
    }
};
 
void bad1()
{
    coroutine h = S{0}.f();
    // S{0} destroyed
    h.resume(); // resumed coroutine executes std::cout << i, uses S::i after free
    h.destroy();
}
 
coroutine bad2()
{
    S s{0};
    return s.f(); // returned coroutine can't be resumed without committing use after free
}
 
void bad3()
{
    coroutine h = [i = 0]() -> coroutine // a lambda that's also a coroutine
    {
        std::cout << i;
        co_return;
    }(); // immediately invoked
    // lambda destroyed
    h.resume(); // uses (anonymous lambda type)::i after free
    h.destroy();
}
 
void good()
{
    coroutine h = [](int i) -> coroutine // make i a coroutine parameter
    {
        std::cout << i;
        co_return;
    }(0);
    // lambda destroyed
    h.resume(); // no problem, i has been copied to the coroutine
                // frame as a by-value parameter
    h.destroy();
}
int main() {
	bad1();
	bad2();
	bad3();
	good();
	return 0;
}

编译指令:

g++ co_await.cc -o co_await -fcoroutines

co_await

下面通过实例具体介绍下co_await的使用,通过co_await实现python生成器的功能,代码如下:

#include <concepts>
#include <coroutine>
#include <exception>
#include <iostream>
// 注意这里使用了嵌套类,但不是必须的,
// 也可以将promise_type定义为顶层类,
// 在task内部使用using promise_type = xxx;
struct task_t {
/*
promise_type 用于定义一类协程的行为,包括协程创建方式、协程初始化完成和
结束时的行为、发生异常时的行为、如何生成 awaiter 的行为以及 co_return 
的行为等等。promise 对象可以用于记录/存储一个协程实例的状态。每个协程桢与
每个 promise 对象以及每个协程实例是一一对应的。
promise_type里的接口需要我们实现,promise_type里的接口是给编译器调用的。
*/
  struct promise_type {
    // 返回给 caller 一个对象。
    task_t get_return_object() { return {}; }
    // 控制协程初始化完成后是否挂起。
    // std::suspend_never 是一个空类,它可以用来表示await表达式永远不会挂起,也不会产生值。
    std::suspend_never initial_suspend() { return {}; }
    // 控制协程执行完后是否挂起。
    std::suspend_never final_suspend() noexcept { return {}; }
    // 处理异常
    void unhandled_exception() {}
  };
};
// awaitable是一个await-expression,
// 例如: co_await awaitable表示表示暂停当前协程的运行
// 转而等待awaitable的结果,最后返回一个awaiter
// 告诉co_await要做什么。
struct awaitable {
  std::coroutine_handle<>* anchor;
  // await_ready告诉co_await准备好没有,如果返回false,
  //    如果await_suspend返回coroutine_handle的一个实例h,
  //    那么恢复这个handle,即运行await_suspend(h).resume()
  //    即暂停本协程同时恢复另一个协程。
  //    如果await_suspend返回bool,如果为false则恢复自己。
  //    如果await_suspend返回void, 那么直接执行,执行完暂停本协程。
  // 如果await_ready返回true或者协程被恢复了,那么执行await_resume,
  // 执行完暂停本协程。
  // 总结下:
  //    await_ready:准备好了没有。
  //    await_suspend:停不停止。 
  //    await_resume:好了做什么。
  constexpr bool await_ready() const noexcept { return false; }
  void await_suspend(std::coroutine_handle<> h) {
    if (anchor) {
      *anchor = h;
      anchor = nullptr;
    }
  }
  constexpr void await_resume() const noexcept {}
};
// coroutine_handle用于引用挂起或正在执行的协程,其模板参数是
// promise_type。
task_t counter(std::coroutine_handle<>* continuation, int start) {
  awaitable a{continuation};
  for (unsigned i = start;; ++i) {
    co_await a;
    std::cout << i << std::endl;
  }
}
 
struct generator_counter {
  int start;
  std::coroutine_handle<> closure_func;
  generator_counter(int begin) : start{begin}, closure_func{} {
    counter(&closure_func, start);
  }
  void next(){
    closure_func();
  }
  ~generator_counter(){
    closure_func.destroy();
  }
};
 
int main() {
  generator_counter count_from_10(10);
  for (int i = 0; i < 5; ++i) {
    count_from_10.next();
  }
  return 0;
}
 

执行结果如下:

10
11
12
13
14

先说到这里,后续会进一步详细作解释,并进一步介绍co_return和co_yield的功能。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值