C++20尝鲜-协程(一)

C++20尝鲜-协程(一)

什么是协程

参考[cppreference](https://zh.cppreference.com/)的解释如下:

A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack. This allows for sequential code that executes asynchronously

从上面的解释可以简单解释下C++20中协程的基本概念

  1. 协程就是一种特殊的函数,它可以在函数执行到某个地方的时候暂停执行,返回给调用者(可以有一个返回值),并允许随后从暂停的地方恢复继续执行。
  2. 协程分为有栈协程和无栈协程,C++20的协程是无栈协程。
  3. 协程可以帮助我们以同步的方式实现异步的逻辑

C++20协程示例

#include <coroutine>
#include <iostream>

struct HelloCoroutine {
    struct HelloPromise {
        HelloCoroutine get_return_object() {
            std::cout << "==============call get_return_object==============" << std::endl;
            return std::coroutine_handle<HelloPromise>::from_promise(*this);
        }
        std::suspend_never initial_suspend() {
            std::cout << "==============call initial_suspend==============" << std::endl;
            return {}; 
        }

        std::suspend_always final_suspend() noexcept {
            std::cout << "==============call final_suspend==============" << std::endl;
            return {}; 
        }
        void unhandled_exception() {}
    };

    using promise_type = HelloPromise;
    HelloCoroutine(std::coroutine_handle<HelloPromise> h) : handle(h) {}

    std::coroutine_handle<HelloPromise> handle;
};

std::coroutine_handle<> coroutine_handle;
struct AWaitableObject
{
    AWaitableObject() {}

    bool await_ready() const { 
        std::cout << "==============call await_ready==============" << std::endl;
        return false; 
    }

    int await_resume() { 
        std::cout << "==============call await_resume==============" << std::endl;
        return 0; 
    }

    void await_suspend(std::coroutine_handle<> handle) {
        std::cout << "==============call await_suspend==============" << std::endl;
        coroutine_handle = handle;
    }

};

HelloCoroutine hello() {
    std::cout << "Hello " << std::endl;
    co_await AWaitableObject{};
    std::cout << "world!" << std::endl;
}

int main() {
    std::cout << "start" << std::endl;

    HelloCoroutine coro = hello();

    std::cout << "calling resume" << std::endl;
    coro.handle.resume();

    std::cout << "destroy" << std::endl;
    coro.handle.destroy();

    return 0;
}

gcc-11.1.0版本编译并运行

root@DESKTOP-QA1H9TD:/mnt/d/Code/os# g++-11 coroutine.cpp -std=c++2a
root@DESKTOP-QA1H9TD:/mnt/d/Code/os# ./a.out
start
==============call get_return_object==============
==============call initial_suspend==============
Hello
==============call await_ready==============
==============call await_suspend==============
calling resume
==============call await_resume==============
world!
==============call final_suspend==============
destroy
root@DESKTOP-QA1H9TD:/mnt/d/Code/os#

观察运行结果发现,在使用了关键字co_await后会返回到caller, 在main中使用resume()后,回到co_await的赋值等式中运行

C++20协程的细节

Results CoroutineFunction(){
	
	co_await AwaitatbleObject();
	
	co_return {};
}

一个协程函数的形式如上所示,当函数体内出现了co_await, co_yield,co_return这三个关键字之后,就会被当成一个协程函数。并且,编译器要求返回值类型包含一个promise_type的结构以及需要实现必要的函数,以上一个例子中的HelloCoroutine类型为例:

struct HelloCoroutine {
    struct HelloPromise {
        HelloCoroutine get_return_object() {
            std::cout << "==============call get_return_object==============" << std::endl;
            return std::coroutine_handle<HelloPromise>::from_promise(*this);
        }
        std::suspend_never initial_suspend() {
            std::cout << "==============call initial_suspend==============" << std::endl;
            return {}; 
        }

        std::suspend_always final_suspend() noexcept {
            std::cout << "==============call final_suspend==============" << std::endl;
            return {}; 
        }
        void unhandled_exception() {}
    };

promise_type放在协程函数返回值类型HelloCoroutine中,用于控制协程的流程。观察打印结果并总结,它在运行过程中主要分为如下三个阶段:

  1. 初始化准备阶段

    • 协程函数运行后,首先生成一个promise_type对象
    • 调用get_return_object()函数创建返回值对象,在协程第一次返回时就会把这个对象返回给caller;
    • 调用initial_suspend()函数,这个返回值有两个选择suspend_never/suspend_always,never表示继续运行,always表示协程挂起,同时把返回值对象返回,所以这个接口的语义是,协程创建后是否马上运行
  2. 运行阶段

    • 开始运行协程函数,如果出现异常会调用unhandled_exception()去处理

    • 例子中用到了co_await关键字,这是一个一元操作符,操作的对象为awaitable类型,就是实现await_ready(), await_resume(), await_suspend( ) 的类型,负责管理协程挂起时的行为,如例子所示的AWaitableObject

      struct AWaitableObject
      {
          AWaitableObject() {}
      
          bool await_ready() const { 
              std::cout << "==============call await_ready==============" << std::endl;
              return false; 
          }
      
          int await_resume() { 
              std::cout << "==============call await_resume==============" << std::endl;
              return 0; 
          }
      
          void await_suspend(std::coroutine_handle<> handle) {
              std::cout << "==============call await_suspend==============" << std::endl;
              coroutine_handle = handle;
          }
      
      };
      

      首先运行await_ready( )函数,判断是否要挂起当前线程: 如果是false,则挂起; 如果是true,则表示不要挂起,然后会调用await_suspend(),用于提供挂起前的处理,然后协程就被挂在这个点

      然后一旦协程被恢复运行时,继续调用**await_resume()**在返回一个值到协程挂起点

      co_await除了显示使用之外,promise_type的接口中凡是返回了suspend_never/suspend_always的地方,编译器都是通过co_await的方式调用这些函数的,suspend_never/suspend_alwaysawaitable类型

      struct suspend_always
        {
          bool await_ready() { return false; }
      
          void await_suspend(coroutine_handle<>) {}
      
          void await_resume() {}
        };
      
        struct suspend_never
        {
          bool await_ready() { return true; }
      
          void await_suspend(coroutine_handle<>) {}
      
          void await_resume() {}
        };
      
    • 如果遇到co_yield var这样的表达式,表示想要挂起当前协程,返回一个值给caller, 编译器相当于调用了yield_value(var)方法,我们可以此时将值设置到Result的相关变量中,编译器会继续根据函数的返回值判断是否为suspend_always判断要返回到caller点。改造原示例如下:

      struct HelloCoroutine {
          struct HelloPromise {
              std::string_view value_;
      
              HelloCoroutine get_return_object() {
                  return std::coroutine_handle<HelloPromise>::from_promise(*this);
              }
              std::suspend_never initial_suspend() { return {}; }
              std::suspend_always final_suspend() noexcept { return {}; }
              void unhandled_exception() {}
      
              std::suspend_always yield_value(std::string_view value) {
                  value_ = value;
                  std::cout << value_ << std::endl;
                  return {};
              }
          };
      
          using promise_type = HelloPromise;
          HelloCoroutine(std::coroutine_handle<HelloPromise> h) : handle(h) {}
      
          std::coroutine_handle<HelloPromise> handle;
      };
      
      HelloCoroutine hello() {
          std::string_view s = "Hello ";
          co_yield s;
      
          std::cout << "world" << std::endl;
      }
      
    • 如果co_return这样的表达式,想要结束协程返回一个对象,则会调用return_value()这个函数,设置好要返回的相关值; 如果整个协程都没有出现co_return,则会调用return_void()

      struct HelloCoroutine {
          struct HelloPromise {
              HelloCoroutine get_return_object() {
                  return std::coroutine_handle<HelloPromise>::from_promise(*this);
              }
              std::suspend_never initial_suspend() { return {}; }
              std::suspend_always final_suspend() { return {}; }
              void unhandled_exception() {}
      
              void return_value(int value) { 
                  std::cout << "got co_return value " << value << std::endl; 
              }
          };
      
          using promise_type = HelloPromise;
          HelloCoroutine(std::coroutine_handle<HelloPromise> h) : handle(h) {}
      
          std::coroutine_handle<HelloPromise> handle;
      };
      
      HelloCoroutine hello() {
          std::cout << "Hello " << std::endl;
          co_await std::suspend_always{};
          std::cout << "world!" << std::endl;
          co_return 42;
      }
      
  3. 结束阶段

    • 最后调用final_suspend()判断协程已处理完毕释放前是否要挂起
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值