c++20协程详解(三)

news2024/11/19 19:27:13

前言

前面两节我们已经能够实现一个可用的协程框架了。但我们一定还想更深入的了解协程,于是我们就想尝试下能不能co_await一个协程。下面会涉及到部分模板编程的知识,主要包括(模板偏特化,模板参数列表传值,模板函数类型推断)可以提前了解下,方便后续理解。

在开始之前我们不妨思考下面一些问题:

  • 异步函数协程 在co_await时的共性是什么?
    两者的操作数都必须是awaiter,这意味着co_await协程,必须能够将协程转换为一个awaiter。
  • 协程生命周期什么时候结束?协程执行到co_return时,或者执行coroutine_handle.destory()时,以及出现未捕获的异常时会销毁协程,释放协程对象promise
  • 协程从开始到结束,会产生几个awaiter,会co_awiter几次?
    2~3次,initial_suspend和final_suspend会产生两个awaiter,同时编译器帮我们进行了co_await, 还有一次是人为的co_await,即我们co_await一个协程。
  • initial_suspend和final_suspend 这两个功能的作用是什么?
    为什么要initial_suspend和final_suspend ,或者说为什么这里可以自由返回可挂起的等待体,为什么提供这个机制?initial_suspend是协程创建后,编译器帮我们co_await,这将允许我们即使不使用co_await,协程函数运行能参与到不同于普通函数的调度中,这直接决定了协程行为上和普通函数的相似程度;final_suspend功能也类似,我们已经知道该函数是在协程执行结束时操作系统使用co_await调用的,如果final_suspend返回的是不挂起操作awaiter,那么协程在执行完后会自动析构promise对象释放资源,而返回挂起awaiter,提供了将协程对象销毁交给用户的协程调度器的可能性。这里还有一个知识点,是对第二篇final_suspend的补充,使用协程还可以实现序列发生器,序列发生器中的协程永远不会调用co_return,所以永远不会结束,当final_suspend不挂起时,编译器也无法分辨出一个协程的生命周期,而这里选择挂起,我们可以明确告诉编译器该协程会结束,有助于编译器帮我们优化。

协程代码实现

我的目标是让c++像在python中一样使用协程。
我的协程实现思路如下:我希望协程表现出的行为尽可能和普通函数一样,所以我不在initial_suspend时挂起协程给协程调度器调度(我直接在该返回的awaiter::await_ready返回true,给编译器提供优化协程为inline的机会);协程应该是和python一样是单线程,所以不会在用户co_await时,交给其他线程处理(这部分功能由上一节异步函数补齐);我希望编译器尽可能能帮我优化代码而且更符合规范,所以我在协程结束时挂起协程,交给调度器去调度销毁。

代码

#include <coroutine>
#include <future>
#include <chrono>
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <memory>
#include <vector>

struct async_task_base
{
    virtual void completed() = 0;
    virtual void resume() = 0;
};


std::mutex m;
std::vector<std::shared_ptr<async_task_base>> g_event_loop_queue; 

std::vector<std::shared_ptr<async_task_base>> g_resume_queue; //多线程异步任务完成后后,待主线程恢复的线程
std::vector<std::shared_ptr<async_task_base>> g_work_queue; //执行耗时操作线程队列

enum class EnumAwaiterType:uint32_t{
    EnumInitial = 1, //协程initial
    EnumSchduling = 2,// 用户co_await
    EnumFinal = 3//销毁
};


template <typename ReturnType>
struct CoroutineTask;

template <typename CoTask, EnumAwaiterType AwaiterType >
struct CommonAwaiter ;


template <typename CoTask, EnumAwaiterType AwaiterType>
struct coroutine_task: public async_task_base{
    coroutine_task(CommonAwaiter<CoTask, AwaiterType> &awaiter)
    :owner_(awaiter)
    {

    }

    void completed() override{
    }

    void resume() override{
        if(owner_.h_.done()){
            owner_.h_.destroy();
        }else{
            owner_.h_.resume();
        }
    }
    CommonAwaiter<CoTask,AwaiterType> &owner_ ;
};

template <typename CoTask, EnumAwaiterType AwaiterType = EnumAwaiterType::EnumSchduling>
struct CommonAwaiter 
{
    using return_type =  typename CoTask::return_type;
    using promise_type = typename CoTask::promise_type;
    CommonAwaiter(promise_type* promise):promise_(promise){
    }

    // 当时initial_suspend返回的awaiter时,挂起,直接resume
    bool await_ready() const noexcept { 
        return false;
    }

    //也可以直接恢复 
    // std::coroutine_handle<> await_suspend(std::coroutine_handle<> h)  {
    //     return h;
    // }

    void await_suspend(std::coroutine_handle<> h)  {
        // std::cout <<"await_suspend()" << std::endl;
        h_ = h;
        g_event_loop_queue.emplace_back(std::shared_ptr<async_task_base>( new coroutine_task<CoTask, AwaiterType>(*this)) );
    }


    return_type await_resume() const noexcept { 
        return promise_->get_value();
    }

    ~CommonAwaiter(){
    }

    bool resume_ready_= false;
    promise_type* promise_ = nullptr;
    std::coroutine_handle<> h_ = nullptr;
};


template <typename CoTask>
struct CommonAwaiter<CoTask, EnumAwaiterType::EnumInitial>
{
    CommonAwaiter(){
    }

    // 当时initial_suspend返回的awaiter时,挂起,跳过await_suspend,直接resume,跳过
    bool await_ready() const noexcept { 
        return true;
    }

    void await_suspend(std::coroutine_handle<>)  {
    }



    void await_resume() const noexcept { 
    }

    ~CommonAwaiter(){
    }
};



// 必须为noexcept,因为这个时候协程已经运行结束,不该有异常产生
template <typename CoTask>
struct CommonAwaiter <CoTask, EnumAwaiterType::EnumFinal>
{
    CommonAwaiter(){
    }

    // 这里不选择true让编译器帮我们自动释放,如果为true编译器不知道什么时候协程结束,无法帮助我们优化
    bool await_ready() noexcept { 
        return false;
    }


    void await_suspend(std::coroutine_handle<> h)  noexcept{
        h_ = h;
        g_event_loop_queue.emplace_back(std::shared_ptr<async_task_base>( new coroutine_task<CoTask, EnumAwaiterType::EnumFinal>(*this)));
    }

    // 无需返回
    void await_resume()  noexcept{ 
    }

    std::coroutine_handle<> h_ = nullptr;
};


template<typename CoTask>
struct Promise
{
    using return_type  = typename CoTask::return_type ;
    ~Promise(){
    //    std::cout << "~Promise" << std::endl;
    }
    CommonAwaiter<CoTask, EnumAwaiterType::EnumInitial> initial_suspend() {
        return {}; 
    };
    
    CommonAwaiter<CoTask, EnumAwaiterType::EnumFinal> final_suspend() noexcept { 
        return {}; 
    }

    // 提供了一种对协程中未捕获的异常的再处理,比如将异常保存下来,实现协程如以下形式 : coroutine().get().catch()
    // 这里我们的实现形式决定了,这里直接再次抛出异常就好
    void unhandled_exception(){
        // try {
        std::rethrow_exception(std::current_exception());
        // } catch (const std::exception& e) {
        //     // 输出异常信息
        //     std::cerr << "Unhandled exception caught in CustomAsyncTask: " << e.what() << std::endl;
        // } catch (...) {
        //     std::cerr << "Unhandled unknown exception caught in CustomAsyncTask!" << std::endl;
        // }
    }

    CoTask get_return_object(){ 
        return  CoTask(this);
    }

    return_type get_value() {
        return value_;
    }


    void return_value(return_type value){
        value_ = value;
    }
   
    // 该代码写在Promise中的好处是,可以方便阅读代码很容易就能回想出协程最多会返回三个等待体
    template<typename T>
    CommonAwaiter<CoroutineTask<T>> await_transform(CoroutineTask<T> &&task){
        return CommonAwaiter<CoroutineTask<T>>(task.p_);
    }

    CoTask await_transform(CoTask &&task){
        return CommonAwaiter<CoTask>(task.p_);
    }


    return_type value_;
};

template <typename ReturnType>
struct CoroutineTask{

    using return_type  = ReturnType;
    using promise_type = Promise<CoroutineTask>;

    CoroutineTask(const CoroutineTask &other) = delete;
    CoroutineTask(const CoroutineTask &&other) = delete;
    CoroutineTask& operator=(const CoroutineTask&) = delete;
    CoroutineTask& operator=(const CoroutineTask&&) = delete;

    CoroutineTask(promise_type* promise) {
        p_ = promise;
        
    }

    promise_type *p_ = nullptr;

};



CoroutineTask<u_int64_t> second_coroutine(){
    co_return 3;
}

CoroutineTask<float> third_coroutine(){
    co_return 3.1;
}


CoroutineTask<char> first_coroutine(){
    uint64_t num =  co_await second_coroutine();
    std::cout << "second_coroutine result is  : " << num  << std::endl; 
    float num2 =  co_await third_coroutine();
    std::cout << "third_coroutine result is  : " << num2  << std::endl; 
    co_return 'b';
}


void do_work() {
    while (1)
    {
        std::lock_guard<std::mutex> g(m);
        for(auto task : g_work_queue){
            task->completed();
            g_resume_queue.push_back(task);
        }
        
        g_work_queue.clear();
    }   
    
}


void run_event_loop(){
    std::vector<std::shared_ptr<async_task_base>> g_raw_work_queue_tmp;
    std::vector<std::shared_ptr<async_task_base>> g_event_loop_queue_temp;
    while(1){
        g_raw_work_queue_tmp.clear();
        g_event_loop_queue_temp.clear();
        {
            g_event_loop_queue_temp.swap(g_event_loop_queue);
            std::lock_guard<std::mutex> g(m);
            g_raw_work_queue_tmp.swap(g_resume_queue);
        }
        
        // 优先恢复耗时任务
        for(auto &task : g_raw_work_queue_tmp){
            task->resume();
        }

        for(auto task : g_event_loop_queue_temp){
            task->resume();
        }

    }
}


void test_func(){
    first_coroutine();
}

int main(){
    test_func();
    std::thread work_thread(do_work);
    run_event_loop();
    return 0;
}

代码分析

Promise

在这里插入图片描述

unhandled_exception

unhandled_exception 的作用是是用来对协程中未捕获的异常再处理。在一些实现协程使用方式为 **coroutine().get().catch()**的架构中,会把未捕获的异常暂存下来,待恢复的时候再抛出。我选择直接抛出异常,因为出现未捕获的异常时,协程也会提前结束,这时reume的结果是未定义的,所以我觉得在resume之前抛出异常有有必要的。

在这里插入图片描述

await_transform

await_transform的作用和重载运算符co_await是一样的,在co_await一个协程时,会转换CoroutineTask为一个awaiter。使用await_transform的优势是,所有等待体的返回时机,都在promise定义出来,方便代码阅读。
这里我们需要注意的是该await_transform需要定义为模板函数,而不能用Promise的类型参数CoTask,作为传入参数类型。

修改代码如下
在这里插入图片描述
编译一下,我们发现报错了
在这里插入图片描述
这里我们再结合代码理解下
在这里插入图片描述
根据报错信息和调用顺序,我们可以得出以下结论:

当前位于CoroutineTask的写成体中,所以对应的promise类型是promise<CoroutineTask>,
这时实例化的await_transform 实际上是 CoroutineTask await_transform (CoroutineTask&&task),而这时await_transform 操作的是协程second_coroutine,协程类型是CoroutineTask<u_int64_t> 类型不一致,所以会出现上面的报错。

CoroutineTask

在这里插入图片描述
协程类CoroutineTask要保存什么?
这里只保存了promise的指针,原因如下:

协程和用户代码交互是通过awaiter对象,由于返回值是通过return_value保存在协程promise中的,我们需要在awaiter从promise获取返回的值,所以需要在awaiter中保存promise的指针,那promise的指针从哪来呢?awaiter是在await_transform中使用CoroutineTask初始化的,而我们又知道CoroutineTask是由promise 调用 get_return_object创建的。所以我们在创建CoroutineTask时,将promise的指针保存进去, 这样awaiter就能够通过CoroutineTask作为中介得到promise的指针啦!

CommonAwaiter

其实讲到这里,CommonAwaiter就没多少能讲的东西了。
awaiter使用偏特化,根据不同枚举,特化了三个版本。来控制协程的基本行为:即创建时不挂起,能够有机会被编译器优化为inline;用户代码挂起能够返回任意co_return返回的值;结束挂起,参与调度销毁。对了还有一个问题协程句柄为什么保存在awaiter中而不是promise中。在我看来awaiter就代表了每个挂起点,所以将couroutine_handle保存在awaiter中,couroutine_handle很小所以不考虑内存开销

运行结果

最后我们运行下代码,完美运行。
这里就不阐述流程了,下一篇会将二、三两节的代码合并起来,集中阐述下流程和汇总下重要的知识点。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1567048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Bat初級

目录 一、編碼發展史 二、Bat運行順序 1、Bat是從上到下逐句運行的。 三、一些Bat元素 1、和echo off 2、注釋 3、pause 4、errorlevel 5、title 6、COLOR 7、GOTO label 和 :label 8、FIND 9、START 目录 一、編碼發展史 二、Bat運行順序 1、Bat是從上到下逐句運行的。 三…

三维VR虚拟展馆打破传统展览的时间与空间限制

探索绿色未来&#xff0c;见证生态转型——这是长江经济带在国家发展蓝图中的承诺。如今&#xff0c;通过线上长江经济带发展阶段性成效展厅&#xff0c;这一承诺正以创新的互动体验呈现给公众&#xff0c;彰显了环境保护与经济增长的和谐统一。 深圳VR公司华锐视点精心策划的长…

学术前沿|通研院提出首个“对称现实”框架,探索智能时代人机共生新范式

人类正在迈入智能时代&#xff0c;其中一个显著特征是大量智能体的涌现。无论生物人、数字人和机器人&#xff0c;都是智能体的不同具身形式。为实现多种智能体在跨虚实空间的共存协作&#xff0c;探索人机共生的新范式&#xff0c;北京通用人工智能研究院&#xff08;简称通研…

Jenkins详细教程(下载安装、构建部署到Linux)

目录 第一章、快速了解Jenkins1.1&#xff09;Jenkins中一些概念介绍1.2&#xff09;Jenkins和maven用途上的区别1.3&#xff09;为什么使用Jenkins 第二章、Winodws下载安装Jenkins2.1&#xff09;安装之前的准备2.2&#xff09;Windows中Jenkins下载安装教程2.3&#xff09;J…

百度行驶证C++离线SDK V1.1 C#接入

百度行驶证C离线SDK V1.1 C#接入 目录 说明 效果 项目 代码 下载 说明 自己根据SDK封装了动态库&#xff0c;然后C#调用。 SDK包结构 效果 项目 代码 using Newtonsoft.Json; using System; using System.Drawing; using System.Runtime.InteropServices; using System…

【Android、 kotlin】kotlin学习笔记

基本语法 fun main(){val a2var b "Hello"println("$ (a - 1} $b Kotlin!")} Variables 只赋值一次用val read-only variables with val 赋值多次用var mutable variables with var Standard output printin() and print() functions String templ…

蓝凌OA单点登录实现方案:以统一身份管理提升效率与安全新举措

蓝凌OA的优势与挑战 在数字化浪潮的推动下&#xff0c;企业对于高效、安全的身份管理需求愈发迫切。蓝凌OA系统&#xff0c;以其出色的流程管理和协同办公能力&#xff0c;已经成为众多企业实现数字化转型的重要工具。然而&#xff0c;随着企业信息化建设的不断深入&#xff0…

2024最新AI创作系统ChatGPT源码+Ai绘画网站源码,GPTs应用、AI换脸、插件系统、GPT文档分析、GPT语音对话一站式解决方案

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

微信小程序怎么制作?制作一个微信小程序需要多少钱?

随着移动互联网的快速发展&#xff0c;微信小程序已成为连接用户与服务的重要桥梁。它以其便捷性和易用性&#xff0c;为各类企业和个人提供了一个全新的展示和交易平台。那么&#xff0c;如何制作一个微信小程序&#xff1f;又需要投入多少资金呢&#xff1f;本文将为您提供全…

权限管理系统【BUG】

1.1.简介 忙里偷闲&#xff0c;学点Java知识。越发觉得世界语言千千万&#xff0c;最核心的还是思想&#xff0c;一味死记硬背只会让人觉得很死板不灵活&#xff0c;嗯~要灵活~ 1.2.问题 permission.js:37 [Vue warn]: Error in render: "TypeError: Cannot read prope…

docker部署nacos,单例模式(standalone),使用mysql数据库

文章目录 前言安装创建文件夹"假装"安装一下nacos拷贝文件夹删除“假装”安装的nacos容器生成nacos所需的mysql表获取mysql-schema.sql文件创建一个mysql的schema 重新生成新的nacos容器 制作docker-compose.yaml文件查看网站 前言 此处有本人写得简易版本安装&…

大话设计模式之备忘录模式

备忘录模式是一种行为设计模式&#xff0c;用于在不破坏封装性的前提下捕获对象的内部状态&#xff0c;并在需要时将其恢复到先前的状态。它允许在不暴露对象实现细节的情况下&#xff0c;保存和恢复对象的状态。 以下是备忘录模式的一种常见实现&#xff1a; 备忘录&#xff…

网络基础二——传输层协议UDP与TCP

九、传输层协议 ​ 传输层协议有UDP协议、TCP协议等&#xff1b; ​ 两个远端机器通过使用"源IP"&#xff0c;“源端口号”&#xff0c;“目的IP”&#xff0c;“目的端口号”&#xff0c;"协议号"来标识一次通信&#xff1b; 9.1端口号的划分 ​ 0-10…

Windows不常见问题集

● 解决CACLS 禁止修改计算机名 管理员权限运行cmd&#xff1a;cacls %SystemRoot%\System32\netid.dll /grant administrators:f ● Excel 2010 AltTab組合鍵設置 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer&#xff0c;在該路徑建32字元DWO…

P1102 A-B 数对 (非二分,不开龙永远的痛,用map解决)

可是我真的会伤心 题目链接 思路&#xff1a;1.本来想的是暴力&#xff0c;两层循环模拟每个数。 2.后来想先把每个数字的个数求出来放在数组nums【】中&#xff0c;并把不重复的数字存到数组b&#xff0c;再两层循环b数组应该时间复杂度会好些&#xff0c;如果b数组中的两个数…

如何在 Visual Studio for Mac 中使用 .NET 8 上的 FastReport Avalonia

FastReport Business Graphics .NET&#xff0c;是一款基于fastreport报表开发控件的商业图形库&#xff0c;借助 FastReport 商业图形库&#xff0c;您可以可视化不同的分层数据&#xff0c;构建业务图表以进行进一步分析和决策。利用数据呈现领域专家针对 .NET 7、.NET Core、…

设计模式之命令模式(上)

命令模式 1&#xff09;概述 1.定义 命令模式(Command Pattern) 将一个请求封装为一个对象&#xff0c;可以用不同的请求对客户进行参数化&#xff1b;对请求排队或者记录请求日志&#xff0c;以及支持可撤销的操作。 2.作用 命令模式可以将请求发送者和接收者完全解耦&am…

京东获得JD商品详情 API 接口(jd.item_get)的详细使用说明,包括如何通过该接口获取商品的基本信息,包括名称、品牌、产地、规格参数等

通过调用京东商品详情API接口&#xff0c;开发者可以获取商品的基本信息&#xff0c;如名称、品牌、产地、规格参数等。此外&#xff0c;还可以获取商品价格信息&#xff0c;包括原价、促销价和活动信息等。同时&#xff0c;该接口还支持获取商品的销量、评价、图片、描述等详细…

Flutter iOS上架指南

本文探讨了使用Flutter开发的iOS应用能否上架&#xff0c;以及上架的具体流程。苹果提供了App Store作为正式上架渠道&#xff0c;同时也有TestFlight供开发者进行内测。合规并通过审核后&#xff0c;Flutter应用可以顺利上架。但上架过程可能存在一些挑战&#xff0c;因此可能…

5.3.1 配置交换机 SSH 管理和端口安全

5.3.1 实验1:配置交换机基本安全和 SSH管理 1、实验目的 通过本实验可以掌握&#xff1a; 交换机基本安全配置。SSH 的工作原理和 SSH服务端和客户端的配置。 2、实验拓扑 交换机基本安全和 SSH管理实验拓扑如图所示。 交换机基本安全和 SSH管理实验拓扑 3、实验步骤 &a…