c++20 协程coroutine
csdh11 2024-11-30 14:11 20 浏览
在c++20中,千呼万唤的协程终于来了,本文将对c++20的协程进行讲解,了解其使用方法。
简介
c++20的协程是无栈协程,通俗讲其是一种可以支持暂停和恢复运行的函数。
为此c++20新引入了3个关键字, co_await,co_yield和co_return,定义包含了上述三个关键字之一的函数是协程。
co_await 表达式——用于暂停执行,直到恢复:
task<> tcp_echo_server()
{
char data[1024];
while (true)
{
std::size_t n = co_await socket.async_read_some(buffer(data));
co_await async_write(socket, buffer(data, n));
}
}
co_yield 表达式——用于暂停执行并返回一个值:
generator<int> iota(int n = 0)
{
while (true)
co_yield n++;
}
co_return 语句——用于完成执行并返回一个值:
lazy<int> f()
{
co_return 7;
}
下面这一大串是我不想讲却又不得不讲的,c++20的协程范式,确实非常复杂,像是写给library的编写者,而不是写给应用层开发者的。上网搜了一下,c++20协程的提出者David Mazières,可以参考他写的文章(My tutorial and take on C++20 coroutinesopen in new window),网上关于其的讨论也非常多,例如下面的论坛c++20协程的讨论open in new window,个人感觉这个协程的设计非常的学院派,不知道工程界的人怎么看...
c++20 coroutine编程范式
如果你想要拥有一个协程,首先要做的是要构建一个promise_type和awaitable类型:
c++20 coroutine要求你定义一个包含 promise_type 的类型,其中 promise_type 又需要至少包含 get_return_object, initial_suspend, final_suspend, return_void 和 unhandled_exception 函数;另外co_await 表达式还要你实现一个 awaitable 类型,这个 awaitable 类型至少需要实现 await_ready, await_suspend 和 await_resume。
接着就是理解promise_type和awaitable类型是如何配合的。
当调用协程函数时,其步骤如下:
- 使用 operator new 申请空间并初始化协程状态;
- 复制协程参数到到协程状态中;
- 构造协程承诺对象 promise;
- 调用 promise.get_return_object() 并将其结果存储在局部变量中。该结果将会在协程首次挂起时返回给调用者;
- 调用 co_await promise.initial_suspend(),预定义了 std::suspend_always 表示始终挂起,std::suspend_never 表示始终不挂起;
- 而后正式开始执行协程函数内过程。
当协程函数执行到 co_return [expr] 语句时:
- 若 expr 为 void 则执行 promise.return_void(),否则执行 promise.return_value(expr);
- 按照创建顺序的倒序销毁局部变量和临时变量;
- 执行 co_await promise.final_suspend()。
当协程执行到 co_yield expr 语句时:
- 执行 co_await promise.yield_value(expr)。
当协程执行到 co_await expr 语句时:
- 通过 expr 获得 awaiter 对象;
- 执行 awaiter.await_ready(),若为 true 则直接返回 awaiter.await_resume();
- 否则将协程挂起并保存状态,执行 awaiter.await_suspend(),若其返回值为 void 或者 true 则成功挂起,将控制权返还给调用者 / 恢复者;
- 直到 handle.resume() 执行后该协程才会恢复执行,将 awaiter.await_resume() 作为表达式的返回值。
当协程因为某个未捕获的异常导致终止时:
- 捕获异常并调用 promise.unhandled_exception();
- 调用 co_await promise.final_suspend()。
当协程状态销毁时(通过协程句柄主动销毁 / co_return 返回 / 未捕获异常):
- 析构 promise 对象;
- 析构传入的参数;
- 回收协程状态内存。
这一串流程是如此的复杂而严谨,让我觉得写代码就像是在写论文一样。。
话不多说,理解上述的流程还是要通过一个例子来看。
//g++ main.cpp -std=c++20
#include <coroutine>
#include <iostream>
#include <thread>
std::coroutine_handle<> handle;
struct ReadAwaiter {
bool await_ready() {
std::cout << "current, no data to read" << std::endl;
return false;
}
void await_resume() {
std::cout << "get data to read" << std::endl;
}
void await_suspend(std::coroutine_handle<> h) {
std::cout << "suspended self, wait data to read" << std::endl;
handle = h;
}
};
struct Promise {
struct promise_type {
auto get_return_object() noexcept {
std::cout << "get return object" << std::endl;
return Promise();
}
auto initial_suspend() noexcept {
std::cout << "initial suspend, return never" << std::endl;
return std::suspend_never{};
}
auto final_suspend() noexcept {
std::cout << "final suspend, return never" << std::endl;
return std::suspend_never{};
}
void unhandled_exception() {
std::cout << "unhandle exception" << std::endl;
std::terminate();
}
void return_void() {
std::cout << "return void" << std::endl;
return;
}
};
};
Promise ReadCoroutineFunc() {
co_await ReadAwaiter();
}
int main() {
ReadCoroutineFunc();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "sleep 1s and then read data" << std::endl;
handle.resume();
}
执行结果如下所示:
get return object
initial suspend, return never
current, no data to read
suspended self, wait data to read
sleep 1s and then read data
get data to read
return void
final suspend, return never
上述例子演示了一开始协程方法没有数据可读,然后挂起,等有数据可读时再恢复协程的运行。
下面来解释一下运行的过程:
- 在main函数中调用了ReadCoroutineFunc函数,该函数是一个coroutine。
- 在进入ReadCoroutineFunc的时候,创建了Promise对象,并调用了Promise对象的get_return_object方法,也调用了Promise对象的initial_suspend方法。可以看到日志打印了get return object和initial suspend, return never。
- 接下来co_await ReadAwaiter()将调用await_ready去判断是否可以运行,由于返回的是false,于是执行了await_suspend,可以看到日志打印了current, no data to read和suspended self, wait data to read。在await_suspend函数中将全局变量handle用于存储协程的运行状态。
- 经过此番操作,ReadCoroutineFunc被挂起,于是继续执行main方法,在sleep 1s之后,打印了sleep 1s and then read data。
- 接着handle.resume()协程将从挂起状态的地方继续执行,于是执行了await_resume方法,于是打印了get data to read。
- 最终协程执行完毕,隐式的co_return,调用了return_void和final_suspend,于是打印了return void和final suspend, return never。
对照代码和协程的范式,虽然可以将原理理清楚,但是其目前的复杂程度还是让我对c++20的协程的第一印象不太好。相较于c++20的无栈协程,目前我还是更愿意使用state-thread或者libco等三方库或者中提供的有栈协程。
总结
- c++20的协程是一个无栈协程,目前使用起来并不方便,有较为复杂的编程范式,个人认为仅仅需要对c++20协程的内容有个大体认识就好,这么原始的接口使用起来还是太麻烦,期待后续的标准对其进行简化,降低使用难度。
相关推荐
- 探索Java项目中日志系统最佳实践:从入门到精通
-
探索Java项目中日志系统最佳实践:从入门到精通在现代软件开发中,日志系统如同一位默默无闻却至关重要的管家,它记录了程序运行中的各种事件,为我们排查问题、监控性能和优化系统提供了宝贵的依据。在Java...
- 用了这么多年的java日志框架,你真的弄懂了吗?
-
在项目开发过程中,有一个必不可少的环节就是记录日志,相信只要是个程序员都用过,可是咱们自问下,用了这么多年的日志框架,你确定自己真弄懂了日志框架的来龙去脉嘛?下面笔者就详细聊聊java中常用日志框架的...
- 物理老师教你学Java语言(中篇)(物理专业学编程)
-
第四章物质的基本结构——类与对象...
- 一文搞定!Spring Boot3 定时任务操作全攻略
-
各位互联网大厂的后端开发小伙伴们,在使用SpringBoot3开发项目时,你是否遇到过定时任务实现的难题呢?比如任务调度时间不准确,代码报错却找不到方向,是不是特别头疼?如今,随着互联网业务规模...
- 你还不懂java的日志系统吗 ?(java的日志类)
-
一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...
- 谈谈枚举的新用法--java(java枚举的作用与好处)
-
问题的由来前段时间改游戏buff功能,干了一件愚蠢的事情,那就是把枚举和运算集合在一起,然后运行一段时间后buff就出现各种问题,我当时懵逼了!事情是这样的,做过游戏的都知道,buff,需要分类型,且...
- 你还不懂java的日志系统吗(javaw 日志)
-
一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...
- Java 8之后的那些新特性(三):Java System Logger
-
去年12月份log4j日志框架的一个漏洞,给Java整个行业造成了非常大的影响。这个事情也顺带把log4j这个日志框架推到了争议的最前线。在Java领域,log4j可能相对比较流行。而在log4j之外...
- Java开发中的日志管理:让程序“开口说话”
-
Java开发中的日志管理:让程序“开口说话”日志是程序员的朋友,也是程序的“嘴巴”。它能让程序在运行过程中“开口说话”,告诉我们它的状态、行为以及遇到的问题。在Java开发中,良好的日志管理不仅能帮助...
- OS X 效率启动器 Alfred 详解与使用技巧
-
问:为什么要在Mac上使用效率启动器类应用?答:在非特殊专业用户的环境下,(每天)用户一般可以在系统中进行上百次操作,可以是点击,也可以是拖拽,但这些只是过程,而我们的真正目的是想获得结果,也就是...
- Java中 高级的异常处理(java中异常处理的两种方式)
-
介绍异常处理是软件开发的一个关键方面,尤其是在Java中,这种语言以其稳健性和平台独立性而闻名。正确的异常处理不仅可以防止应用程序崩溃,还有助于调试并向用户提供有意义的反馈。...
- 【性能调优】全方位教你定位慢SQL,方法介绍下!
-
1.使用数据库自带工具...
- 全面了解mysql锁机制(InnoDB)与问题排查
-
MySQL/InnoDB的加锁,一直是一个常见的话题。例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?下面是不同锁等级的区别表级锁:开销小,加锁快;不会出现死锁;锁定粒度...
- 看懂这篇文章,你就懂了数据库死锁产生的场景和解决方法
-
一、什么是死锁加锁(Locking)是数据库在并发访问时保证数据一致性和完整性的主要机制。任何事务都需要获得相应对象上的锁才能访问数据,读取数据的事务通常只需要获得读锁(共享锁),修改数据的事务需要获...
- 一周热门
- 最近发表
- 标签列表
-
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- parsevideo (33)
- 个人网站源码 (37)
- centos7.4下载 (33)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- jdk1.8.0_191下载 (33)
- axure9注册码 (33)
- pts/1 (33)
- spire.pdf 破解版 (35)
- shiro jwt (35)
- sklearn中文手册pdf (35)
- itextsharp使用手册 (33)
- 凯立德2012夏季版懒人包 (34)
- 反恐24小时电话铃声 (33)
- 冒险岛代码查询器 (34)
- 128*128png图片 (34)
- jdk1.8.0_131下载 (34)