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

C++20协程示例

C++20协程示例

认识协程

在C++中,协程就是一个可以暂停和恢复的函数。

包含co_waitco_yieldco_return关键字的都可以叫协程。

看一个例子:

MyCoroGenerator<int> testFunc(int n)
{std::cout << "Begin testFunc" << std::endl;for (int i = 0; i < n; ++i) {std::cout << "TestFunc before yield " << i << std::endl;co_yield i;std::cout << "TestFunc after yield " << i << std::endl;}std::cout << "End testFunc" << std::endl;
}int main()
{int inp = 10;std::cout << "Before testFunc" << std::endl;MyCoroGenerator<int> gen = testFunc(inp);std::cout << "After testFunc" << std::endl;for (int i = 0; i < inp; ++i) {std::cout << "Cur input: " << i << std::endl;std::cout << "Output value: " << gen.next() << std::endl;std::cout << "After input: " << i << std::endl;}
}

上面这段代码的执行结果是:

Before testFunc
After testFunc
Cur input: 0
Output value: Begin testFunc
TestFunc before yield 0
0
After input: 0
Cur input: 1
Output value: TestFunc after yield 0
TestFunc before yield 1
1
After input: 1
Cur input: 2
Output value: TestFunc after yield 1
TestFunc before yield 2
2
After input: 2
Cur input: 3
Output value: TestFunc after yield 2
TestFunc before yield 3
3
After input: 3
Cur input: 4
Output value: TestFunc after yield 3
TestFunc before yield 4
4
After input: 4
Cur input: 5
Output value: TestFunc after yield 4
TestFunc before yield 5
5
After input: 5...

调用时发现,函数并没有一开始就执行,而是等到next时才开始执行。执行到co_yield就会自动暂停,下次next才会继续执行

另外,函数本身并没有返回什么,但是这里可以使用MyCoroGenerator<int>接收。函数的控制权也转移到了MyCoroGenerator<int>,需要执行时就调用next

函数在暂停后执行,依然可以继续上次的状态,这是因为,协程在挂起期间,其上下文全部都被保留,下次执行时恢复。

协程句柄

std::coroutine_handle类模板是实现协程的最底层的工具,负责存储协程的句柄。他可以特化为std::coroutine_handle<Promise>或者std::coroutine_handle<void>

这里面的Promise是实现协程必要的Promise类,而且其名字必须是promise_type

协程会暂停,其上下文也会保留,std::coroutine_handle就是保存和管理协程的地方。

std::coroutine_handle中方法不多,但是每个都很重要,其中:

  • done方法,可以查询协程是否已经结束
  • resume,恢复一个协程的执行
  • destroy,销毁一个协程

std::coroutine_handle是一个很底层的东西,没有RAII包裹,就像一个裸指针那样,需要手动创建、手动销毁。

所以需要一个包裹类,负责处理协程句柄的初始化和销毁,也就是Generator

Generator

C++的协程要求Generator必须有promise_type这个类,名字也必须这个,不能是其他的名字,直接定义在Generator中最方便。

template <typename T>
struct MyCoroGenerator {struct promise_type {// todo};
};

promise_type中有一些固定接口,这些接口如果不实现,那么协程是不完整的。

  • initial_suspend,协程是否在初始化结束后挂起,返回std::suspend_always、std::suspend_never,这是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
  • final_suspend,协程最后一次执行是否挂起,返回值和上面函数相同。由于final_suspend是收尾阶段的工作,所以必须是noexcept
  • unhandled_exception,处理协程中未被处理的异常
  • get_return_object,返回一个Generator对象
  • yield_value,处理协程返回值,就是co_yield传递过来的值
  • return_void,处理协程结束后的返回值,和return_value同时只能存在一个,如果没有返回值就用return_void
  • return_value,处理协程结束后返回值,和return_void同时只能存在一个,如果有返回值就用return_value

解释一下yield_value函数,co_yield实际是一个语法糖,相当于co_wait promise.yield_value(i),只有实现了该函数,Genreator才能接收co_yield的返回值

函数的返回值,需要回答协程要不要挂起,也就是std::suspend_always或者std::suspend_never,因为需要yield后挂起,所以返回std::suspend_always

为了接收返回值,需要用转发和std::optional,接收并存储返回值。

get_return_object负责创建一个Generator,一般来说,使用std::coroutine_handle<promise_type>::from_promise接口从一个Promise创建句柄,然后使用这个句柄创建Generator。

为了配合创建函数,MyGenerator需要实现一个接收句柄的构造函数。也就是MyCoroGenerator(std::coroutine_handle<promise_type> h)

#include <coroutine>
#include <iostream>
#include <optional>template <typename T>
struct MyCoroGenerator {/*** @brief C++协程要求Generator必须有promise_type这个类型,名字也必须是promise_type* 最方便的方法就是定义在Generator类内部**/struct promise_type {/*** @brief 存放协程返回值* 由Generator从获取并返回*/std::optional<T> opt;/*** @brief 协程是否创建时就被挂起,函数名字也必须是initial_suspend* std::suspend_always、std::suspend_never是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起** @return std::suspend_always 协程创建即挂起*/std::suspend_always initial_suspend() const{return {};}/*** @brief 协程最后一次执行是否挂起,函数名字也必须是final_suspend* 由于final_suspend是收尾阶段的工作,所以必须是noexcept** @return std::suspend_always 协程最后一次执行也被挂起*/std::suspend_always final_suspend() const noexcept{return {};}/*** @brief 处理协程中未捕获异常,函数名字必须是unhandled_exception**/void unhandled_exception(){std::exit(EXIT_FAILURE);}/*** @brief 获取一个Generator对象,该对象从promise_type构造** @return MyCoroGenerator*/MyCoroGenerator get_return_object(){return MyCoroGenerator { std::coroutine_handle<promise_type>::from_promise(*this) };}/*** @brief 定制yield_value接口,接收co_yield返回的值** @tparam Arg 值的类型* @param arg co_yield返回值* @return std::suspend_always 执行完后继续挂起*/template <typename Arg>std::suspend_always yield_value(Arg&& arg){opt.emplace(std::forward<Arg>(arg));return {};}/*** @brief 当协程结束co_return且没有返回值时,调用该函数* 还有一个return_value(expr)函数,负责处理协程结束且有返回值的情况*/void return_void(){}};/*** @brief 协程句柄,存储了协程上下文,一个非常底层的东西,没有RAII* 用MyCoroGenerator包裹*/std::coroutine_handle<promise_type> handle;/*** @brief 默认构造函数**/MyCoroGenerator() = default;/*** @brief 通过一个handle构造一个Generator** @param h 从promise_type构造出来的协程句柄*/MyCoroGenerator(std::coroutine_handle<promise_type> h): handle(h){}/*** @brief 移动构造函数** @param other 其他Generator对象*/MyCoroGenerator(MyCoroGenerator&& other){if (handle) {handle.destroy();}handle = other.handle;other.handle = nullptr;}/*** @brief 析构函数**/~MyCoroGenerator(){if (handle) {handle.destroy();}}/*** @brief 移动赋值函数** @param other 其他Generator对象* @return MyCoroGenerator& 当前镀锡*/MyCoroGenerator& operator=(MyCoroGenerator&& other){if (handle) {handle.destroy();}handle = other.handle;other.handle = nullptr;return *this;}/*** @brief 继续执行协程,并返回执行结果** @return T& 返回的值*/T& next(){handle.resume();if (handle.done()) {// throw geneator_done("Generator done");throw "Generator Error";}return *(handle.promise().opt);}private:MyCoroGenerator(const MyCoroGenerator&) = delete;MyCoroGenerator& operator=(const MyCoroGenerator&) = delete;
};

源码

#include <coroutine>
#include <iostream>
#include <optional>template <typename T>
struct MyCoroGenerator {/*** @brief C++协程要求Generator必须有promise_type这个类型,名字也必须是promise_type* 最方便的方法就是定义在Generator类内部**/struct promise_type {/*** @brief 存放协程返回值* 由Generator从获取并返回*/std::optional<T> opt;/*** @brief 协程是否创建时就被挂起,函数名字也必须是initial_suspend* std::suspend_always、std::suspend_never是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起** @return std::suspend_always 协程创建即挂起*/std::suspend_always initial_suspend() const{return {};}/*** @brief 协程最后一次执行是否挂起,函数名字也必须是final_suspend* 由于final_suspend是收尾阶段的工作,所以必须是noexcept** @return std::suspend_always 协程最后一次执行也被挂起*/std::suspend_always final_suspend() const noexcept{return {};}/*** @brief 处理协程中未捕获异常,函数名字必须是unhandled_exception**/void unhandled_exception(){std::exit(EXIT_FAILURE);}/*** @brief 获取一个Generator对象,该对象从promise_type构造** @return MyCoroGenerator*/MyCoroGenerator get_return_object(){return MyCoroGenerator { std::coroutine_handle<promise_type>::from_promise(*this) };}/*** @brief 定制yield_value接口,接收co_yield返回的值** @tparam Arg 值的类型* @param arg co_yield返回值* @return std::suspend_always 执行完后继续挂起*/template <typename Arg>std::suspend_always yield_value(Arg&& arg){opt.emplace(std::forward<Arg>(arg));return {};}/*** @brief 当协程结束co_return且没有返回值时,调用该函数* 还有一个return_value(expr)函数,负责处理协程结束且有返回值的情况*/void return_void(){}};/*** @brief 协程句柄,存储了协程上下文,一个非常底层的东西,没有RAII* 用MyCoroGenerator包裹*/std::coroutine_handle<promise_type> handle;/*** @brief 默认构造函数**/MyCoroGenerator() = default;/*** @brief 通过一个handle构造一个Generator** @param h 从promise_type构造出来的协程句柄*/MyCoroGenerator(std::coroutine_handle<promise_type> h): handle(h){}/*** @brief 移动构造函数** @param other 其他Generator对象*/MyCoroGenerator(MyCoroGenerator&& other){if (handle) {handle.destroy();}handle = other.handle;other.handle = nullptr;}/*** @brief 析构函数**/~MyCoroGenerator(){if (handle) {handle.destroy();}}/*** @brief 移动赋值函数** @param other 其他Generator对象* @return MyCoroGenerator& 当前镀锡*/MyCoroGenerator& operator=(MyCoroGenerator&& other){if (handle) {handle.destroy();}handle = other.handle;other.handle = nullptr;return *this;}/*** @brief 继续执行协程,并返回执行结果** @return T& 返回的值*/T& next(){handle.resume();if (handle.done()) {// throw geneator_done("Generator done");throw "Generator Error";}return *(handle.promise().opt);}private:MyCoroGenerator(const MyCoroGenerator&) = delete;MyCoroGenerator& operator=(const MyCoroGenerator&) = delete;
};MyCoroGenerator<int> testFunc(int n)
{std::cout << "Begin testFunc" << std::endl;for (int i = 0; i < n; ++i) {std::cout << "TestFunc before yield " << i << std::endl;co_yield i;std::cout << "TestFunc after yield " << i << std::endl;}std::cout << "End testFunc" << std::endl;
}int main()
{int inp = 10;std::cout << "Before testFunc" << std::endl;MyCoroGenerator<int> gen = testFunc(inp);std::cout << "After testFunc" << std::endl;for (int i = 0; i < inp; ++i) {std::cout << "Cur input: " << i << std::endl;std::cout << "Output value: " << gen.next() << std::endl;std::cout << "After input: " << i << std::endl;}
}

参考:C++ coroutine generator 实现笔记

相关文章:

C++20协程示例

C20协程示例 认识协程 在C中&#xff0c;协程就是一个可以暂停和恢复的函数。 包含co_wait、co_yield、co_return关键字的都可以叫协程。 看一个例子&#xff1a; MyCoroGenerator<int> testFunc(int n) {std::cout << "Begin testFunc" << s…...

【Verilog 教程】6.2Verilog任务

关键词&#xff1a;任务 任务与函数的区别 和函数一样&#xff0c;任务&#xff08;task&#xff09;可以用来描述共同的代码段&#xff0c;并在模块内任意位置被调用&#xff0c;让代码更加的直观易读。函数一般用于组合逻辑的各种转换和计算&#xff0c;而任务更像一个过程&a…...

Spring修炼之路(1)基础入门

一、简介 1.1Spring概述 Spring框架是一个轻量级的Java开发框架&#xff0c;它提供了一系列底层容器和基础设施&#xff0c;并可以和大量常用的开源框架无缝集成&#xff0c;可以说是开发Java EE应用程序的必备。Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器&…...

GANs学习记录

GAN 基于GAN的研究识别相关不同背景目标图像 可以用Augmentation2021.3.15 基于GAN的研究 是通过GAN 进行图像重建&#xff0c;恢复细节&#xff0c;去模糊&#xff0c;提高图像质量&#xff0c;图像还原&#xff0c;去噪等等。 识别相关 一种基于生成对抗网络的训练样本扩充…...

Flink-CDC——MySQL、SqlSqlServer、Oracle、达梦等数据库开启日志方法

目录 1. 前言 2. 数据源安装与配置 2.1 MySQL 2.1.1 安装 2.1.2 CDC 配置 2.2 Postgresql 2.2.1 安装 2.2.2 CDC 配置 2.3 Oracle 2.3.1 安装 2.3.2 CDC 配置 2.4 SQLServer 2.4.1 安装 2.4.2 CDC 配置 2.5达梦 2.4.1安装 2.4.2CDC配置 3. 验证 3.1 Flink版…...

linux设置tomcat redis开机自启动

设置Tomcat自启动 1.修改 /etc/rc.d/rc.local 文件 [rootiowZ]# vim /etc/rc.d/rc.local在/etc/rc.d/rc.local文件最后加上&#xff1a; export JAVA_HOME/usr/local/jdk /usr/local/apache-tomcat-8.5.73/bin/startup.sh start退出vim并保存修改的文件。 说明&#xff1a;/u…...

跨域问题讨论

问题 跨域定义 当一个请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域。 跨域的安全隐患&#xff08;CSRF攻击&#xff09; 也就是说&#xff0c;一旦允许跨域&#xff0c;意味着允许恶意网站随意攻击可信网站&#xff0c;带来安全风险。 这里面有一…...

ESP32设备通信-两个ESP32设备之间HTTP通信

两个ESP32设备之间HTTP通信 文章目录 两个ESP32设备之间HTTP通信1、应用介绍2、软件准备3、硬件准备4、代码实现4.1 ESP32服务器节点代码4.2 ESP32客户端节点代码在本文中,我们将介绍如何在没有任何物理路由器或互联网连接的情况下使用 Wi-Fi 在两个 ESP32 开发板之间执行无线…...

数据结构学习笔记——查找算法中的树形查找(平衡二叉树)

目录 一、平衡二叉树的定义二、平衡因子三、平衡二叉树的插入和构造&#xff08;一&#xff09;LL型旋转&#xff08;二&#xff09;LR型旋转&#xff08;三&#xff09;RR型旋转&#xff08;四&#xff09;RL型旋转 四、平衡二叉树的删除&#xff08;一&#xff09;叶子结点&a…...

P1830 轰炸III

题目背景 一个大小为 &#xfffd;&#xfffd;nm 的城市遭到了 &#xfffd;x 次轰炸&#xff0c;每次都炸了一个每条边都与边界平行的矩形。 题目描述 在轰炸后&#xff0c;有 &#xfffd;y 个关键点&#xff0c;指挥官想知道&#xff0c;它们有没有受到过轰炸&#xff0c;如…...

大语言模型LLM知多少?

你知道哪些流行的大语言模型?你都体验过哪写? GPT-4,Llamma2, T5, BERT 还是 BART? 1.GPT-4 1.1.GPT-4 模型介绍 GPT-4(Generative Pre-trained Transformer 4)是由OpenAI开发的一种大型语言模型。GPT-4是前作GPT系列模型的进一步改进,旨在提高语言理解和生成的能力,…...

Redis命令行使用Lua脚本

Redis命令行使用Lua脚本 Lua脚本在Redis中的使用非常有用&#xff0c;它允许你在Redis服务器上执行自定义脚本&#xff0c;可以用于复杂的数据处理、原子性操作和执行多个Redis命令。以下是Lua脚本在Redis中的基本使用详细讲解&#xff1a; 运行Lua脚本&#xff1a; 在Redis中…...

HTML详细基础(三)表单控件

本帖介绍web开发中非常核心的标签——表格标签。 在日常我们使用到的各种需要输入用户信息的场景——如下图&#xff0c;均是通过表格标签table创造出来的&#xff1a; 目录 一.表格标签 二.表格属性 三.合并单元格 四.无序列表 五.有序列表 六.自定义标签 七.表单域 …...

map和set的具体用法 【C++】

文章目录 关联式容器键值对setset的定义方式set的使用 multisetmapmap的定义方式insertfinderase[]运算符重载map的迭代器遍历 multimap 关联式容器 关联式容器里面存储的是<key, value>结构的键值对&#xff0c;在数据检索时比序列式容器效率更高。比如&#xff1a;set…...

聚合统一,SpringBoot实现全局响应和全局异常处理

目录 前言 全局响应 数据规范 状态码(错误码) 全局响应类 使用 优化 全局异常处理 为什么需要全局异常处理 业务异常类 全局捕获 使用 优化 总结 前言 在悦享校园1.0版本中的数据返回采用了以Map对象返回的方式&#xff0c;虽然较为便捷但也带来一些问题。一是在…...

【C/C++笔试练习】——数组名和数组名、switch循环语句、数据在计算机中的存储顺序、字符串中找出连续最长的数字串、数组中出现次数超过一半的数字

文章目录 C/C笔试练习1.数组名和&数组名&#xff08;1&#xff09;数组名和&数组名的差异&#xff08;2&#xff09;理解数组名和指针偏移&#xff08;3&#xff09;理解数组名代表的含义&#xff08;4&#xff09;理解数组名代表的含义 2.switch循环语句&#xff08;6…...

力扣每日一题(+日常水题|树型dp)

740. 删除并获得点数 - 力扣&#xff08;LeetCode&#xff09; 简单分析一下: 每一个数字其实只有2个状态选 or 不 可得预处理每一个数初始状态(不选为0,选为所有x的个数 * x)累加即可 for(auto &x : nums)dp[x][1] x;每选一个树 i 删去 i 1 和 i - 1 故我们可以将 i…...

使用perming加速训练可预测的模型

监督学习模型的训练流程 perming是一个主要在支持CUDA加速的Windows操作系统上架构的机器学习算法&#xff0c;基于感知机模型来解决分布在欧式空间中线性不可分数据集的解决方案&#xff0c;是基于PyTorch中预定义的可调用函数&#xff0c;设计的一个面向大规模结构化数据集的…...

【数据库】存储引擎InnoDB、MyISAM、关系型数据库和非关系型数据库、如何执行一条SQL等重点知识汇总

目录 存储引擎InnoDB、MyISAM的适用场景 关系型和非关系型数据库的区别 MySQL如何执行一条SQL的 存储引擎InnoDB、MyISAM的适用场景 InnoDB 是 MySQL 默认的事务型存储引擎&#xff0c;只有在需要它不支持的特性时&#xff0c;才考虑使用其它存储引擎。实现了四个标准的隔…...

车道线分割检测

利用opencv&#xff0c;使用边缘检测、全局变化梯度阈值过滤、算子角度过滤、HLS阈值过滤的方法进行车道线分割检测&#xff0c;综合多种阈值过滤进行检测提高检测精度。 1.利用cv2.Sobel()计算图像梯度(边缘检测) import cv2 import numpy as np import matplotlib.pyplot a…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...