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_always,suspend_never,Awaiter。前两个是标准库中已经定义好的等待体,suspend_always为挂起当前协程,suspend_never不挂起当前协程,Awaiter是我们自定义的。观察这三个类,我们不难发现都有三个成员函数await_ready,await_suspend,await_resume,所以只要有这三个成员函数的类,都是等待体,都可以通过co_await挂起协程,这里我们还知道了另外一个知识点,即co_await的操作数是awaiter
coroutine(协程):Result,promise_type,到这里了我们引出一个知识点c++中怎么定义一个协程?当一个函数的返回值的类型中有promise_type这个类,就是协程。那么promise_type和Result之间的关系是什么呢?Result只是一个外壳,由于这个类中有个类型叫promise_type,在这个函数调用时,编译器就会为之生成一些协程代码。promise_type 就是编译器实际操作的对象,这个类中必须包含下面几个函数供编译器调用:initial_suspend ,final_suspend,get_return_object,return_void或return_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协程第一篇,主要是让大家对协程的定义,以及协程的执行流有一个初步的认识,后面还会出两篇对协程的高阶封装。 在开始正式开始协程之前,请务必记住,c协程 不是挂起当前协程,转而执行其他协程&a…...

go: go.mod file not found in current directory or any parent directory.如何解决?
这个错误表明你正在执行 go get 命令,但是当前目录或任何父目录中都找不到 go.mod 文件。这可能是因为你的项目还没有使用 Go Modules 进行管理。 要解决这个问题,有几种方法: go mod init <module-name> 其中 <module-name>…...
Go-Gin全局错误处理中间件
为了防止报错引起Gin服务挂掉以及错误日志记录,我们使用全局错误中间件进行管理。 package middlewareimport ("ToDoList/global""github.com/gin-gonic/gin""go.uber.org/zap""net""net/http""net/http/h…...

图神经网络实战(6)——使用PyTorch构建图神经网络
图神经网络实战(6)——使用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 环境...

毕马威:《智慧之眼:开启汽车感知新时代》
在全球科技飞速发展和产业革新的大潮中,汽车产业正在以前所未有的速度向网联化、智能化的方向转型。汽车传感器作为智能联网汽车发展的关键环节之一,扮演着举足轻重的角色。 毕马威一直关注汽车产业的变化与发展,为了更好地为汽车行业赋能&a…...
每日三个JAVA经典面试题(三十四)
1.Mybatis的一级、二级缓存 MyBatis提供了两种缓存机制来提高查询效率:一级缓存和二级缓存。 一级缓存(Session级别) 作用范围:一级缓存是基于SqlSession的。这意味着,如果你在同一个SqlSession中执行两次相同的查询…...

C# 学习第五弹——语句
一、if语句 —简单if语句 —if else 语句 —if else if else 语句 1、简单if语句 if(表达式){语句} (1)表达式必须使用圆括号括起来; (2)表达式:关系表达式或逻辑表达…...
什么是Java中的JVM(Java虚拟机)?它如何工作?
Java中的JVM,全称Java Virtual Machine(Java虚拟机),是Java程序的运行环境,也是Java语言的核心和基础。它是一个虚拟的计算机,具有完善的硬体架构,如处理器、堆栈、寄存器等,以及相应…...

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

代码随想录阅读笔记-二叉树【合并二叉树】
题目 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节…...
Day35:学习尚上优选项目
学习计划:完成尚硅谷的尚上优选项目 学习进度:尚上优选项目 知识点: 四、 搭建平台管理端前端环境 权限管理模块-用户管理 开发为用户分配角色接口用户管理前端测试 权限管理模块-菜单管理 菜单管理需求菜单表设计开发菜单管理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,选择xwr68xx还是xwr64xx,及需要注意的问题 文章目录 demo工程out_of_box文件调试bin文件名称需要注意的问题附录:结构框架雷达基本原理叙述雷达天线排列位置芯片框架Demo工程功能CCS工程导…...

连接Redis不支持集群错误,ERR This instance has cluster support disabled,解决方案
1. 问题背景 调整redis的配置后,启动程序时, 会报如下错误: [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),是轻量级的文本数据交换格式,独立于语言: JSON使用JavaScript语法来描述数据对象,但是JSON仍然独立于语言和平台,JSON解析器和JSON库支持许多不同的编程语言ÿ…...

网络编程套接字应用分享【Linux C/C++ 】【UDP应用 | TCP应用 | TCP线程池小项目】
目录 前提知识 1. 理解源ip,目的ip和Macip 2. 端口号 3. 初识TCP,UDP协议 4. 网络字节序 5. socket 编程 sockaddr类型 一,基于udp协议编程 1. socket——创建套接字 2. bind——将套接字强绑定 3. recvfrom——接受数据 4. s…...
有关数据开发项目中使用HIVE由于无法update和delete的场景下,如何解决数据增量的思路
解决数据增量问题的思路在Hive中 在数据开发项目中,使用Hive进行数据处理时,由于Hive不支持update和delete语句,处理数据增量可能会变得有些棘手。然而,有几种策略和技术可以帮助我们解决这个问题,并确保数据增量的高…...

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

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

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...

Linux-进程间的通信
1、IPC: Inter Process Communication(进程间通信): 由于每个进程在操作系统中有独立的地址空间,它们不能像线程那样直接访问彼此的内存,所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...