C++20四大之二:coroutines特性详解

上一篇博客中我们介绍了C++20中module相关的特性,本篇博客我们通过三个可运行的完整示例来体验下C++20中的协程:coroutine。
全文共三部分,第一部分从概念上讨论协程与普通函数的区别;第二部分通过两个个完整的协程代码示例,并深入到编译器层面,深入解析promise_type及其工作流程;第三部分介绍了co_await的作用以及工作原理,该部分是本文最难理解的部分。

什么是C++的协程?

  • 从语法角度讲,函数代码中含有co_await、co_yield、co_return中任何一个关键字,这个函数就是一个协程。
  • 从系统角度讲,协程是运行在线程中的一堆代码,这些代码的执行过程可以被中断、恢复,从而实现单线程下的异步。这点是协程异步跟多线程异步的根本区别。
    在多线程异步的设计中,代码挂起意味着运行堆栈的保存与cpu等硬件资源的调度。保存与调度由系统负责,一个线程有且只有一个运行堆栈,线程恢复时,从上次挂起的地方继续执行。
    在协程中,代码的“挂起”与硬件资源的调度不再挂钩:我们挂起一段协程,线程继续运行,CPU等硬件资源不会被剥夺。
  • 从执行流程的角度讲,调用一个普通函数,只有两个状态:
    调用(invoke) => 终止(finalize)。
    调用一个协程,有四种状态:
    调用(invoke) => 挂起(suspends) <=> 恢复(resume) => 终止(finalize)。

在非协程的情况下,同一线程下,调用一个函数,一定是从函数第一行开始执行,执行流程要想返回到它的调用者,也只有一个方式:return(不考虑异常),函数return之后,假如再次调用函数,依然一定是从函数的第一行开始执行。
协程的情况下,调用一个协程函数,这个协程函数可能挂起多次、恢复多次,协程可以通过co_yeild挂起且向调用者返回一个值,下次调用从上次返回的语句下方继续执行。。。
在这里插入图片描述

挂起协程时需要保存代码的调用状态、内部变量的值等,保存在哪里呢?
谁来保存?谁来恢复?
一个协程可能返回多次值给caller,这些“返回值如何传递?(非协程代码通过return机制,比如放到eax寄存器中)

带着上面灵魂三问,我们正式进入C++20的coroutines的世界。

协程帧、promise_type 、future_type 与 coroutine_handle

首先牢记两个前提
C++20 的协程没有协程调度器,协程的挂起、恢复,由编译器安插代码完成。
C++20提供了协程机制,而不是提供协程库,Library Write可以使用协程机制实现自己的协程库。

先看下与协程相关的三个关键字:co_await、co_yield与co_return

  • co_yield some_value: 保存当前协程的执行状态并挂起,返回some_value给调用者
  • co_await some_awaitable: 如果some_awaitable没有ready,就保存当前协程的执行状态并挂起
  • co_return some_value: 彻底结束当前协程,返回some_value给协程调用者

我们先根据C++20标准来实现一个最简单的、可运行的、没有返回值的协程(运行环境:https://godbolt.org/ 编译器版本选择”x86-64 gcc (coroutines)“)

#include<iostream>
#include<coroutine>
struct future_type{
   
    struct promise_type;
    using co_handle_type = std::coroutine_handle<promise_type>;

    struct promise_type{
   
        promise_type(){
   
            std::cout<<"promise_type constructor"<<std::endl;
        }
        ~promise_type(){
   
            std::cout<<"promise_type destructor"<<std::endl;
        }
        auto get_return_object(){
   
            std::cout<<"get_return_object"<<std::endl;
            return co_handle_type::from_promise(*this);
        }
        auto initial_suspend(){
   
            std::cout<<"initial_suspend"<<std::endl;
            return std::suspend_always();
        }
        auto final_suspend() noexcept(true) {
   
            std::cout<<"final_suspend"<<std::endl;
            return std::suspend_always();
        }
        void return_void(){
   
            std::cout<<"return_void"<<std::endl;
        }
        void unhandled_exception(){
   
            std::cout<<"unhandled_exception"<<std::endl;
            std::terminate();
        }
    };
    
    future_type(co_handle_type co_handle){
   
        std::cout<<"future_type constructor"<<std::endl;
        co_handle_ = co_handle;
    }
    ~future_type(){
   
        std::cout<<"future_type destructor"<<std::endl;
        co_handle_.destroy();
    }
    future_type(const future_type&) = delete;
    future_type(future_type&&) = delete;

    bool resume(){
   
        if(!co_handle_.done()){
   
            co_handle_.resume();
        }
        return !co_handle_.done();
    }
private:
    co_handle_type co_handle_;
};

future_type three_step_coroutine(){
   
    std::cout<<"three_step_coroutine begin"<<std::endl;
    co_await std::suspend_always();
    std::cout<<"three_step_coroutine running"<<std::endl;
    co_await std::suspend_always();
    std::cout<<"three_step_coroutine end"<<std::endl;
}
int main(){
   
    future_type ret = three_step_coroutine(); 
    std::cout<<"=======calling first resume======"<<std::endl;
    ret.resume();
    std::cout<<"=======calling second resume====="<<std::endl;
    ret.resume();
    std::cout<<"=======calling third resume======"<<std::endl;
    ret.resume();
    std::cout<<"=======main end======"<<std::endl;

    return 0;
}

输出为:

promise_type constructor
get_return_object
initial_suspend
future_type constructor
=======calling first resume======
three_step_coroutine begin
=======calling
  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值