当前位置: 首页 > news >正文

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挂起。

相关文章:

c++20协程详解(一)

前言 本文是c协程第一篇&#xff0c;主要是让大家对协程的定义&#xff0c;以及协程的执行流有一个初步的认识&#xff0c;后面还会出两篇对协程的高阶封装。 在开始正式开始协程之前&#xff0c;请务必记住&#xff0c;c协程 不是挂起当前协程&#xff0c;转而执行其他协程&a…...

go: go.mod file not found in current directory or any parent directory.如何解决?

这个错误表明你正在执行 go get 命令&#xff0c;但是当前目录或任何父目录中都找不到 go.mod 文件。这可能是因为你的项目还没有使用 Go Modules 进行管理。 要解决这个问题&#xff0c;有几种方法&#xff1a; go mod init <module-name> 其中 <module-name>…...

Go-Gin全局错误处理中间件

为了防止报错引起Gin服务挂掉以及错误日志记录&#xff0c;我们使用全局错误中间件进行管理。 package middlewareimport ("ToDoList/global""github.com/gin-gonic/gin""go.uber.org/zap""net""net/http""net/http/h…...

图神经网络实战(6)——使用PyTorch构建图神经网络

图神经网络实战&#xff08;6&#xff09;——使用PyTorch构建图神经网络 0. 前言1. 传统机器学习与人工智能2. 人工神经网络基础2.1 人工神经网络组成2.2 神经网络的训练 3. 图神经网络4. 使用香草神经网络执行节点分类4.1 数据集构建4.2 模型构建4.3 模型训练 5. 实现香草图神…...

【Flutter】windows环境配置

windows 11 环境 官方教程 配置了flutter 环境变量在系统的path里 bin 路径。 死活没反应 关闭了git关闭了dart.exe关闭了vs还是不行卸载重新来 新版git flutter doctor 还需要android 环境...

毕马威:《智慧之眼:开启汽车感知新时代》

在全球科技飞速发展和产业革新的大潮中&#xff0c;汽车产业正在以前所未有的速度向网联化、智能化的方向转型。汽车传感器作为智能联网汽车发展的关键环节之一&#xff0c;扮演着举足轻重的角色。 毕马威一直关注汽车产业的变化与发展&#xff0c;为了更好地为汽车行业赋能&a…...

每日三个JAVA经典面试题(三十四)

1.Mybatis的一级、二级缓存 MyBatis提供了两种缓存机制来提高查询效率&#xff1a;一级缓存和二级缓存。 一级缓存&#xff08;Session级别&#xff09; 作用范围&#xff1a;一级缓存是基于SqlSession的。这意味着&#xff0c;如果你在同一个SqlSession中执行两次相同的查询…...

C# 学习第五弹——语句

一、if语句 —简单if语句 —if else 语句 —if else if else 语句 1、简单if语句 if&#xff08;表达式&#xff09;{语句} &#xff08;1&#xff09;表达式必须使用圆括号括起来&#xff1b; &#xff08;2&#xff09;表达式&#xff1a;关系表达式或逻辑表达…...

什么是Java中的JVM(Java虚拟机)?它如何工作?

Java中的JVM&#xff0c;全称Java Virtual Machine&#xff08;Java虚拟机&#xff09;&#xff0c;是Java程序的运行环境&#xff0c;也是Java语言的核心和基础。它是一个虚拟的计算机&#xff0c;具有完善的硬体架构&#xff0c;如处理器、堆栈、寄存器等&#xff0c;以及相应…...

OmniGraffle Pro for mac 出色的图形设计软件

OmniGraffle Pro是一款非常出色的图形设计软件&#xff0c;它主要适用于Mac和iPad平台&#xff0c;可以用来轻松绘制各种精美的图表、示意图和界面设计。 软件下载&#xff1a;OmniGraffle Pro for mac中文注册激活版 以下是OmniGraffle Pro的一些主要特点和功能&#xff1a; 界…...

代码随想录阅读笔记-二叉树【合并二叉树】

题目 给定两个二叉树&#xff0c;想象当你将它们中的一个覆盖到另一个上时&#xff0c;两个二叉树的一些节点便会重叠。 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠&#xff0c;那么将他们的值相加作为节点合并后的新值&#xff0c;否则不为 NULL 的节…...

Day35:学习尚上优选项目

学习计划&#xff1a;完成尚硅谷的尚上优选项目 学习进度&#xff1a;尚上优选项目 知识点&#xff1a; 四、 搭建平台管理端前端环境 权限管理模块-用户管理 开发为用户分配角色接口用户管理前端测试 权限管理模块-菜单管理 菜单管理需求菜单表设计开发菜单管理CRUD接口开…...

c模板编程c/c++20240401

c模板编程 #include<iostream> //#include<string> //#include<algorithm> template <typename T> T max(T a, T b) { return (a > b) ? a : b; } int main() { int i max(1, 2); // 返回 2 float f max(3.14f, 2.72f); // 返回 3…...

【TI毫米波雷达】IWR6843AOP的官方文件资源名称BUG,选择xwr68xx还是xwr64xx,及需要注意的问题

【TI毫米波雷达】IWR6843AOP的官方文件资源名称BUG&#xff0c;选择xwr68xx还是xwr64xx&#xff0c;及需要注意的问题 文章目录 demo工程out_of_box文件调试bin文件名称需要注意的问题附录&#xff1a;结构框架雷达基本原理叙述雷达天线排列位置芯片框架Demo工程功能CCS工程导…...

连接Redis不支持集群错误,ERR This instance has cluster support disabled,解决方案

1. 问题背景 调整redis的配置后&#xff0c;启动程序时&#xff0c; 会报如下错误&#xff1a; [redis://172.16.0.8xxx]: ERR This instance has cluster support disabledSuppressed: io.lettuce.core.RedisCommandExecutionException: ERR This instance has cluster supp…...

什么是json?json可以存放哪几种数据类型

JSON指的是JavaScript对象表示法(avaScript Object Notation)&#xff0c;是轻量级的文本数据交换格式&#xff0c;独立于语言: JSON使用JavaScript语法来描述数据对象&#xff0c;但是JSON仍然独立于语言和平台&#xff0c;JSON解析器和JSON库支持许多不同的编程语言&#xff…...

网络编程套接字应用分享【Linux C/C++ 】【UDP应用 | TCP应用 | TCP线程池小项目】

目录 前提知识 1. 理解源ip&#xff0c;目的ip和Macip 2. 端口号 3. 初识TCP&#xff0c;UDP协议 4. 网络字节序 5. socket 编程 sockaddr类型 一&#xff0c;基于udp协议编程 1. socket——创建套接字 2. bind——将套接字强绑定 3. recvfrom——接受数据 4. s…...

有关数据开发项目中使用HIVE由于无法update和delete的场景下,如何解决数据增量的思路

解决数据增量问题的思路在Hive中 在数据开发项目中&#xff0c;使用Hive进行数据处理时&#xff0c;由于Hive不支持update和delete语句&#xff0c;处理数据增量可能会变得有些棘手。然而&#xff0c;有几种策略和技术可以帮助我们解决这个问题&#xff0c;并确保数据增量的高…...

两数之和-考察哈希表的运用

题目 给定一个整数数组 n u m s nums nums和一个整数目标值 t a r g e t target target&#xff0c;请你在该数组中找出和为目标值 t a r g e t target target的那 两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同…...

视觉检测系统,外观细节无可挑剔

在传统行业中&#xff0c;利用人工检测来检测产品外观缺陷依然是主流&#xff0c;但由于竞争的加剧&#xff0c;对企业生产效率的要求也越来越高。传统的检测产品外观缺陷问题的方法就是透过人工目检&#xff0c;或者工人采用游标卡尺等工具检测&#xff0c;此种方式检测速度慢…...

C++中string容器的字符串操作

目录 1.c_str() 返回C常量字符串 2.date() 返回C常量字符串 3.substr() 构造子串 4.find() 正向查找&#xff08;查找失败返回npos&#xff09; 5.rfind() 逆向查找&#xff08;查找失败返回npos&#xff09; 6.find_first_of() 正向查找匹配的字符 7.find_last_of() 逆向…...

Java编程使用CGLIB动态代理介绍与实战演示

文章目录 前言技术积累核心概念主要功能适用场景与JDK动态代理的对比 实战演示定义待代理的目标类实现MethodInterceptor接口使用代理对象 测试结果写在最后 前言 在Java编程中&#xff0c;CGLIB (Code Generation Library) 是一个强大的高性能代码生成库&#xff0c;它通过生…...

vue3 渲染一个后端返回的图片字段渲染、table表格内放置图片

一、后端直接返回图片url 当图片字段接口直接返回的是图片url&#xff0c;可以直接放到img标签上 <img v-if"thumbLoader" class"r-image-loader-thumb" :src"resUrl" /> 二、当图片字段接口直接返回的是图片Id 那么就需要去拼一下图片…...

iOS开发进阶(十三):脚手架创建iOS项目

文章目录 一、前言二、xcode-select 命令三、拓展阅读 一、前言 项目初期&#xff0c;需要搭建项目基本框架&#xff0c;为此离不开辅助工具&#xff0c;即脚手架。当然&#xff0c;IDE也可以实现新建空白项目&#xff0c;但是其新建后的项目结构可能不符合预期设计&#xff0…...

手机无线投屏到windows11电脑

1 安装无线投影组件 2 电脑端打开允许其他设备投影的开关 3 手机找到投屏选项 4 手机搜索可用设备连接即可 这里的官方文档给的不太好,给了一些让人眼花撩乱的信息,以下是经过整合的有效信息...

linux 环境安装配置

安装java17 1.下载安装包 wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz 2.解压到自定义目录/usr/local/java mkdir /usr/local/java tar zxvf jdk-17_linux-x64_bin.tar.gz -C /usr/local/java 3.配置环境变量 echo export PATH$PATH:/…...

Git常用语句

设置用户名 git config --global user.name "用户名" git config --global user.email "邮箱"查看git用户信息 cat ~/.gitconfig初始化本地库 git initclone指定分支的代码 git clone -b my_branch gitgitlabxxxxxxxxxxxxxxxxxxxxxx.gitpush三件套 gi…...

坦克大战_java源码_swing界面_带毕业论文

一. 演示视频 坦克大战_java源码_swing界面_带毕业论文 二. 实现步骤 完整项目获取 https://githubs.xyz/y22.html 部分截图 启动类是 TankClinet.java&#xff0c;内置碰撞检测算法&#xff0c;线程&#xff0c;安全集合&#xff0c;一切皆对象思想等&#xff0c;是java进阶…...

JVM 记录

记录 工具 https://gceasy.io 资料 尚硅谷宋红康JVM全套教程&#xff08;详解java虚拟机&#xff09; https://www.bilibili.com/video/BV1PJ411n7xZ?p361 全套课程分为《内存与垃圾回收篇》《字节码与类的加载篇》《性能监控与调优篇》三个篇章。 上篇《内存与垃圾回收篇…...

Linux学习笔记————C 语言版 LED 灯实验

这里写目录标题 一、实验程序编写二、 汇编部分实验程序编写三、C 语言部分实验程序编写四、编译下载验证 汇编 LED 灯实验中&#xff0c;我们讲解了如何使用汇编来编写 LED 灯驱动&#xff0c;实际工作中是很少用到汇编去写嵌入式驱动的&#xff0c;毕竟汇编太难&#xff0c;而…...

wordpress文章 插件/微商推广哪家好

Internet 的50年口述历史第四部分&#xff0c;讲述了微软同 Netscape 的浏览器之争&#xff0c;同时讲述了微软最终如何在反托拉斯案中获胜&#xff0c;成功地将 IE 同 Windows 捆绑并获得历史性胜利。Internet 的50年口述历史的其它章节请参阅&#xff0c;第一部分&#xff0c…...

zblog做企业网站/自己怎么注册网站

U-Boot&#xff0c;全称 Universal Boot Loader&#xff0c;是遵循GPL条款的开放源码项目。U-Boot的作用是系统引导。 U-Boot从FADSROM、8xxROM、PPCBOOT逐步发展演化而来。其源码目录、编译形式与Linux内核很相似&#xff0c;事实上&#xff0c;不少U-Boot源码就是根据相应的…...

徐州做汽车销售的公司网站/西安seo推广

内存泄漏概念&#xff1a;内存泄漏&#xff08;Memory Leak&#xff09;是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费&#xff0c;导致程序运行速度减慢甚至系统崩溃等严重后果。 一、单例模式使用Activity作为Context 单例…...

北京网站建设费用/友情链接交换形式有哪些

本发明涉及计算机安全技术领域&#xff0c;具体地说是一种实用性强、基于Linux的邮件监控方法。背景技术&#xff1a;随着计算机的普及以及人们对个人信息安全的重视&#xff0c;如何保障计算机乃至个人信息的安全成为了一个至关重要的问题。传统的计算机安全设备往往基于病毒及…...

福建建设银行官方网站/2021年中国关键词

本文已迁移到 xuanyiliu.com BTW 我自己已经把项目从Jenkins迁移到了BuddyBuild上&#xff0c;如果你只有一个项目&#xff0c;公司不限制或是个人项目&#xff0c;完全可以用BB来代替&#xff0c;BB配置很简单&#xff0c;按步骤就行了。如果是公司要自己搭建持续集成服&#…...

世界500强企业logo/seo的中文含义

经过这一段时间的实践&#xff0c;我写了这一篇文章&#xff0c;来总结一下怎么样确定服务器性能问题是否与CPU、内存或者磁盘I/O与网络有关 。首先我想介绍的命令:vmstat &#xff1a; [oracletest ~]$ vmstat procs -----------memory---------- ---swap-- -----io---- …...