c++20协程详解(一)

前言

本文是c++协程第一篇,主要是让大家对协程的定义,以及协程的执行流有一个初步的认识,后面还会出两篇对协程的高阶封装。
在开始正式开始协程之前,请务必记住,c++协程 不是挂起当前协程,转而执行其他协程,待 其他协程完成后在恢复当前协程,而是挂起当前协程,转而执行挂起点(awaiter)任务,待该任务结束后,恢复当前协程执行流。话不多说,下面我们直接开始看代码。
看这篇文章之前最好先看下c++20协程的官方手册

创建一个基础的协程

接下来我们创建一个最基本协程

code


#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_resume_queue; //原来的 eventloop队列
std::vector<std::shared_ptr<async_task_base>> g_work_queue; //执行耗时操作线程队列


template <typename T>
struct AsyncAwaiter;


using namespace std::chrono_literals;


 struct suspend_always
  {
     bool await_ready() const noexcept { 
      try
      {
        std::cout << "suspend_always::await_ready" << std::endl;
      }
      catch(const std::exception& e)
      {
        std::cerr << e.what() << '\n';
      }
      return false; 
    }

     void await_suspend(std::coroutine_handle<> handle) const noexcept {
      try
      {
        std::cout << "suspend_always::await_suspend" << std::endl;
      }
      catch(const std::exception& e)
      {
        std::cerr << e.what() << '\n';
      }
      
    }

     void await_resume() const noexcept {
      try
      {
        std::cout << "suspend_always::await_resume" << std::endl;
      }
      catch(const std::exception& e)
      {
        std::cerr << e.what() << '\n';
      }
    }
  };

 struct suspend_never
  {
     bool await_ready() const noexcept { 
      try
      {
        std::cout << "suspend_never::await_ready" << std::endl;
      }
      catch(const std::exception& e)
      {
        std::cerr << e.what() << '\n';
      }
      return true; 
    }

     void await_suspend(std::coroutine_handle<> handle) const noexcept {
      try
      {
        std::cout << "suspend_never::await_suspend" << std::endl;
      }
      catch(const std::exception& e)
      {
        std::cerr << e.what() << '\n';
      }
      
    }

     void await_resume() const noexcept {
      try
      {
        std::cout << "suspend_never::await_resume" << std::endl;
      }
      catch(const std::exception& e)
      {
        std::cerr << e.what() << '\n';
      }
    }
  };


struct Result {
  struct promise_type {
    promise_type(){
      std::cout << "promise_type" << std::endl;
    }

    ~promise_type(){
      std::cout << "~promise_type" << std::endl;
    }
    suspend_never initial_suspend() {
      std::cout << "initial_suspend" << std::endl;
      return {};
    }

    suspend_never final_suspend() noexcept {
      std::cout << "final_suspend" << std::endl;
      return {};
    }

    Result get_return_object() {
      std::cout << "get_return_object" << std::endl;

      return {};
    }
    void return_void() {
        std::cout << "return_void" << std::endl;
    }

//    void return_value(int value) {

//    }

    void unhandled_exception() {

    }
  };
};


template <typename ReturnType>
struct  AsyncThread
{
    using return_type = ReturnType;

    AsyncThread(std::function<return_type ()>&& func): func_(func){

    }
    std::function<return_type ()> func_;
};


template <typename ReturnType>
struct async_task: public async_task_base{
    async_task(AsyncAwaiter<ReturnType> &awaiter)
    :owner_(awaiter)
    {

    }

    void completed() override{
        std::cout << "async_task ::  completed ############" << std::endl;
        ReturnType result = owner_.func_();
        owner_.value_ = result;
    }

    void resume() override{
        std::cout << "async_task ::  resume ############" << std::endl;
        owner_.h_.resume();
    }
    AsyncAwaiter<ReturnType> &owner_ ;
};


template <typename ReturnType>
struct AsyncAwaiter
{
    using return_type = ReturnType;

    AsyncAwaiter(AsyncThread<ReturnType>& info){
        // std::cout<< " AsyncAwaiter(AsyncThread<ReturnType>& info)" << std::endl;
        value_ = return_type{};
        func_ = info.func_;
    }


    // 该awaite直接挂起
    bool await_ready() const noexcept { 
        return false; 
    }
    
    void await_suspend(std::coroutine_handle<> h)  {
        h_ = h;
        std::lock_guard<std::mutex> g(m);
        g_work_queue.emplace_back(std::shared_ptr<async_task_base>( new async_task<uint64_t>(*this)));
    }

    return_type await_resume() const noexcept { 
        // std::cout<< "AsyncAwaiter::await_resume" << std::endl;
        return value_;
    }

  
    std::function<return_type ()> func_;
    std::coroutine_handle<> h_; 
    return_type value_ = return_type();
};


template<typename T>
inline AsyncAwaiter<T> operator co_await(AsyncThread<T>&& info)
{
    return AsyncAwaiter(info);
}


template <typename ReturnType>
AsyncThread<ReturnType> do_slow_work(std::function< ReturnType () > &&func){
    return AsyncThread<ReturnType>(std::forward< std::function< ReturnType () > >(func));
}


Result Coroutine() {
    int a = 1;
    auto func =[&]() -> uint64_t{
        // std::cout<< "do a slow work !!!!!!!!!!!!!!!!!!!!!" << std::endl;
        return a;
    };    
    uint64_t result = co_await do_slow_work<uint64_t>(func);
    std::cout << "@@@@@@@@@ result1 is  : " << result  << std::endl;  
    a = 2;
    result = co_await do_slow_work<uint64_t>(func);
    std::cout << "@@@@@@@@@ result2 is  : " << result  << std::endl; 
    a = 3;
    result = co_await do_slow_work<uint64_t>(func);
    std::cout << "@@@@@@@@@ result3 is  : " << result  << std::endl; 
    co_return;
};


void do_work() {
    while (1)
    {
        // 加锁
        // std::cout << "void do_work()  "   << std::endl;
        // std::this_thread::sleep_for(std::chrono::seconds(1)); //!!!!!还有这个加锁要在锁钱前不然,让出cpu后,由于还没有解锁,又会被其他线程再拿到锁,这样就死锁了

        std::lock_guard<std::mutex> g(m);

        // std::cout << " g_work_queue size " << g_resume_queue.size()   << std::endl;

        for(auto task : g_work_queue){
            task->completed();
            g_resume_queue.push_back(task);
        }
        
        // g_resume_queue.assign(g_work_queue.begin(), g_work_queue.end());   //!!!!!!!这里有个大坑坑查了好久,如果连续两次先进来这里,会把g_raw_work_queue中的元素给清理掉,导致后面无法恢复
        g_work_queue.clear();
        // std::cout << " g_resume_queue size " << g_resume_queue.size()   << std::endl;
    }   
    
}

void run_event_loop(){
    std::vector<std::shared_ptr<async_task_base>> g_raw_work_queue_tmp;
    while(1){
        g_raw_work_queue_tmp.clear();
        // std::this_thread::sleep_for(std::chrono::seconds(1)); 
        {
            std::lock_guard<std::mutex> g(m);
            
            // for(auto &task : g_resume_queue){
            //     task->resume();
            // }
            g_raw_work_queue_tmp.swap(g_resume_queue);
        }

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

void test_func(){
    Coroutine();
}

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


代码分析

在上述代码中我们可以看到有下面几个类

awaiter(等待体):suspend_alwayssuspend_neverAwaiter。前两个是标准库中已经定义好的等待体,suspend_always为挂起当前协程,suspend_never不挂起当前协程,Awaiter是我们自定义的。观察这三个类,我们不难发现都有三个成员函数await_readyawait_suspendawait_resume,所以只要有这三个成员函数的类,都是等待体,都可以通过co_await挂起协程,这里我们还知道了另外一个知识点,即co_await的操作数是awaiter
coroutine(协程):Resultpromise_type,到这里了我们引出一个知识点c++中怎么定义一个协程?当一个函数的返回值的类型中有promise_type这个类,就是协程。那么promise_typeResult之间的关系是什么呢?Result只是一个外壳,由于这个类中有个类型叫promise_type,在这个函数调用时,编译器就会为之生成一些协程代码。promise_type 就是编译器实际操作的对象,这个类中必须包含下面几个函数供编译器调用:initial_suspendfinal_suspendget_return_objectreturn_voidreturn_value。这几个函数的调用时机如下:调用一个协程函数时,首先会调用get_return_object 返回协程对象Result,这个函数只会调用一次,无论是否使用co_await;然后会执行initial_suspend,然后继续执行该协程函数体,直到co_return,这时就会调用return_void或者return_value,将完成后的值作为返回值返回给co_await前的对象;最后会执行final_suspend至此一个协程生命周期结束,释放promise_type对象。

协程基本流程初步认识

接下来我们我们运行下上面的代码看看具体流程
在这里插入图片描述

接下来我们描述下上面的过程吧

1.在main函数中调用Coroutine这个协程函数
2.调用get_return_object 返回一个一个Result对象
3.调用initial_suspend返回等待体suspend_never,
4.由于suspend_never的await_ready返回为true,则不调用await_suspend,直接调用await_resume
5.接下来运行到co_await Awaiter
6. Awaiter 的await_ready返回false,意味这需要在当前位置挂起协程
7.执行到Awaiter::await_suspend函数体,让该协程句柄在2s后恢复执行
8.恢复执行后,调用await_resume,将Awaiter中的值返回,一般情况下该值是某些耗时操作完成后的值
9.执行到co_return调用return_void函数
10.最后执行到final_suspend,
11.final_suspend返回的等待体的 是suspend_always ,由于 await_ready返回 false,则 进入await_suspend,挂起当前协程,由于挂起了当前协程,所以没有销毁promise_type对象,这回造成内存泄漏。

接下来修改final_suspend协程执行完不挂起

修改如下
在这里插入图片描述
得到结果我们发现promise_type对象被释放了
在这里插入图片描述
这是你或许会有一个问题?为什么要在协程执行结束挂起。这个问题其实也一种困扰着我, 在网上看到最多的就是说:去销毁一些资源,但是没有一个能够举出例子要销毁那些?别的地方不能销毁吗?所以我不是很认可。直到看到一篇博客,说到这是一种规范,如果不挂起的话,你没法通过coroutine_handle.done得知协程状态,这是一种规范,交给程序员自行销毁,更规范些,这我才觉得稍微合理些。

我们再修改下

在这里插入图片描述
运行结果如下:

在这里插入图片描述
这里我们发现在协程体内,只有co_await一个awaiter,才会走await_ready后续的流程,但是initial_suspend和final_suspend都会自动被co_await。我们一般会选择initial_suspend不挂起,final_suspend挂起。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Shell脚本之基本语法

目录 一、变量定义 变量命名规则&#xff1a; 变量的赋值&#xff1a; 只读变量&#xff1a; 删除变量&#xff1a; 二、变量的类型 自定义变量&#xff1a; 环境变量&#xff1a; 位置参数&#xff1a; 预定义变量&#xff1a; 三、键盘输入 四、数值运算 为什么…

Failed to resolve import “Home/components/HomeNew.vue“. Does the file exist?

错误信息 [plugin:vite:import-analysis] Failed to resolve import "/apis/home.js" from "src/views/Home/components/HomeNew.vue". Does the file exist? 错误原因 路径错误 解决方法

mysql-FIND_IN_SET包含查询

如图所示&#xff0c;需要查询字段ancestorid中包含14的所有数据&#xff0c;使用FIND_IN_SET即可实现&#xff0c;不需要使用模糊查找like 示例sql&#xff1a; SELECT * FROM mt_fire_template WHERE FIND_IN_SET(14,ancestorid) 结果

安装Pillow库的方法最终解答!_Python第三方库

安装Python第三方库Pillow 我的环境&#xff1a;Window10&#xff0c;Python3.7&#xff0c;Anaconda3&#xff0c;Pycharm2023.1.3 pillow库 Pillow库是一个非常强大的图像处理库。它提供了广泛的图像处理功能&#xff0c;让我们可以轻松地读取和保存图像、创建缩略图和合并到…

LeetCode-94. 二叉树的中序遍历【栈 树 深度优先搜索 二叉树】

LeetCode-94. 二叉树的中序遍历【栈 树 深度优先搜索 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;递归解题思路二&#xff1a;迭代解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1…

【Redis系列】Redis安装与使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Maven 项目之快速选择环境配置文件

Maven项目中&#xff0c;多环境之间如何进行配置文件的切换。在我们开发的过程中&#xff0c;经常会出现开发环境、测试环境、生产环境等之间的切换&#xff0c;如果我们每次都去替换配置文件&#xff0c;就会跟繁琐&#xff0c;这个时候就可以创建多个环境&#xff0c;同时在对…

vscode + wsl1 搭建远程C/C++开发环境

记录第一次搭建环境过程。 搭建C/C开发环境有很多种方式&#xff0c;如 MinGW vscode&#xff08;MinGW 是GCC的Windows版本&#xff0c;本地编译环境&#xff09;SSH隧道连接 vscode&#xff08;远程Linux主机&#xff09;wsl vscode&#xff08;远程Linux环境&#xff09…

要我说,鹅蛋脸才是YYDS!

在国漫的海洋中&#xff0c;女性角色的设定总是千变万化。她们或温婉如水&#xff0c;或刚烈如火&#xff0c;但总有一种难以言喻的东方美贯穿其中。个人比较喜欢玄机科技他们家的审美和建模&#xff0c;特别是那些拥有鹅蛋脸型的角色&#xff0c;她们往往给人以柔和亲切的第一…

『51单片机』蜂鸣器

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…

PHP+python高校教务处工作管理系统q535p

开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp/Laravel 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 运行环境:phpstudy/wamp/xammp等 系统根据现有的管理模块进行开发和扩展&#xff0c;采用面向对象的开发的思想和结构化的开发方…

Linux上管理文件系统

Linux上管理文件系统 机械硬盘 机械硬盘由多块盘片组成&#xff0c;它们都绕着主轴旋转。每块盘片上下方都有读写磁头悬浮在盘片上下方&#xff0c;它们与盘片的距离极小。在每次读写数据时盘片旋转&#xff0c;读写磁头被磁臂控制着不断的移动来读取其中的数据。 所有的盘片…

vite.config.js

Vue3vite vite和webpack区别&#xff1f; 1.vite服务器启动速度比webpack快&#xff0c;由于vite启动的时候不需要打包&#xff0c;也就无需分析模块依赖、编译&#xff0c;所以启动速度非常快。当浏览器请求需要的模块时&#xff0c;再对模块进行编译&#xff0c;这种按需动态…

日历插件fullcalendar【前端】

日历插件fullcalendar【前端】 前言版权开源推荐日历插件fullcalendar一、下载二、初次使用日历界面示例-添加事件&#xff0c;删除事件 三、汉化四、动态数据五、前后端交互1.环境搭建-前端搭建2.环境搭建-后端搭建3.代码编写-前端代码fullcalendar.htmlfullcalendar.js 4.代码…

问题解决:写CSDN博文时图片大小不适应,不清晰,没法排版

项目环境&#xff1a; Window10&#xff0c;Edge123.0.2420.65 问题描述&#xff1a; 当我在CSDN写博文的时候&#xff0c;会经常插入一些图片&#xff0c;但有时候我插入的图片太大了&#xff0c;影响了整体排版。 比如我加入了一张图片&#xff0c;就变成了下面这个样子&…

Compose 中状重组

一、状态变化 1.1 状态变化是什么 根据上篇文章的讲解&#xff0c;在 Compose 我们使用 State 来声明一个状态&#xff0c;当状态发生变化时&#xff0c;则会触发重组。那么状态变化是指什么呢&#xff1f; 下面我们来看一个例子&#xff1a; Composable fun NumList() {val…

【深度学习|Pytorch】torchvision.datasets.ImageFolder详解

ImageFolder详解 1、数据准备2、ImageFolder类的定义transforms.ToTensor()解析 3、ImageFolder返回对象 1、数据准备 创建一个文件夹&#xff0c;比如叫dataset&#xff0c;将cat和dog文件夹都放在dataset文件夹路径下&#xff1a; 2、ImageFolder类的定义 class ImageFol…

C# WPF编程-元素绑定

C# WPF编程-元素绑定 将元素绑定到一起绑定表达式绑定错误绑定模式代码创建绑定移除绑定使用代码检索绑定多绑定绑定更新绑定延时 绑定到非元素对象Source属性RelativeSource属性DataContent属性 数据绑定是一种关系&#xff0c;该关系告诉WPF从源对象提取一下信息&#xff0c;…

296个地级市GDP相关数据集(2000-2023年)

01、数据简介 GDP&#xff0c;即国内生产总值&#xff08;Gross Domestic Product&#xff09;&#xff0c;是指一个国家或地区所有常住单位在一定时期内生产活动的最终成果。 名义GDP&#xff0c;也称货币GDP&#xff0c;是指以生产物品和劳务的当年销售价格计算的全部最终产…

OpenHarmony实战:CMake方式组织编译的库移植

以double-conversion库为例&#xff0c;其移植过程如下文所示。 源码获取 从仓库获取double-conversion源码&#xff0c;其目录结构如下表&#xff1a; 表1 源码目录结构 名称描述double-conversion/cmake/CMake组织编译使用到的模板double-conversion/double-conversion/源…