C 20 协程初探

【导读】:C 20 终于引入了协程特性,给库作者提供了一个实现协程的机制,让用户方便使用协程来编写异步逻辑,降低了异步并发编程的难度。结合我最近协程的学习,在这里记录一下相关内容。

以下是正文


使用场景

协程和普通函数相比,多了个中途随时 挂起 ,随后 恢复 的过程,当用户调用一个阻塞请求接口,从而让出控制权,当响应时,恢复之前的控制流,从而大大提高线程复用率,这也注意了协程只是并发的,并不是真正意义上的并行,在 IO 密集型场景下,协程能够很好的提高资源利用率,用少数的线程达到并发成百上万个协程的效果。

而相对传统的线程池 回调模式,每发起一个请求,为了避免阻塞当前线程,需要挂一个回调函数处理后续过程,而回调函数又可能产生竞争,导致得加锁处理。而协程却能够以同步方式写实现异步,后续过程直接挂起,当响应的时候恢复执行。

我参与的项目中,对象随时都可能起个线程干活,或者常驻于对象生命周期里,统计下来整个项目居然开了几百个线程,由于多线程编程难免导致竞争,从而需要锁这种很低级的机制做同步,而一旦引入了锁,就不可避免的扩散开来,大家看到这里加把锁,那我也加把锁,统计下来代码里面居然也有几百把锁。真是维护的噩梦。

由于协程能够随时挂起,后续恢复,这就能实现一些延迟计算的特性,例如生成器。

扯远了,本文主题是关于 C 20 的协程,在 C 20 还没稳定之前,先来学习一下相关知识,读完本文后你应该能利用这个机制实现一些想要的协程了。

概念模型

C 20 的协程设计为无栈协程,相对于有栈协程,省掉了上下文切换开销[1],只能手动切换,效率更高,也不用管理复杂的寄存器状态,移植性更好,但这同时也导致了不能被非协程函数嵌套调用。

同时引入了 3 个关键字:

1. co_yield: 挂起并返回值

2. co_await: 挂起

3. co_return: 结束协程

当一个函数出现了上面的关键字,则该函数是个协程。


Promise

当 caller 调用一个 callee 协程的时候,协程自身的状态信息 [2](形参,局部变量,自带数据,各个阶段点执行点)会被保存在堆上的 Promise 对象中,这也是编译器会在协程里面插入 Promise 相关代码,以及一些执行点。由于 Promise 的大小可以在编译期计算出来,从而避免了内存浪费。而 Promise 对象所有权可由coroutine_handle 句柄持有。

Future

而 Future 对象主要是与 Promise 对象交互的桥梁,既 caller 与 callee 之间的通信:

1. callee 挂起时,将值返回给 caller: yield 语义

2. callee 执行结束时,将值返回给 caller: return 语义

3. callee 恢复时,caller 将值带给 callee

需要注意的是,这些概念和标准库的 std::promise/std::future 不是同一个东西,后者用于做同步用,std::future会阻塞等待直到 std::promise 提供值,可以看做是条件变量的封装,同样地,和其他语言的 Promise/Future 概念也不一样。

Awaitable

如果一个对象是 Awaitable 对象,那么可以用 co_await 操作符去触发该对象的动作 ready/suspend/resume,从而转移、恢复控制权,co_await 细节留到后面在介绍。

具体机制

了解了概念模型后,我们可以进一步探讨背后的机制了。

Promise/Future 对象

当一个协程被调用时,会创建 Promise 对象,然后编译器会在各个阶段插入一些代码[3]:

{  co_await promise.initial_suspend();  try  {      }  catch (...)  {    promise.unhandled_exception();  }FinalSuspend:  co_await promise.final_suspend();}

可以看到一个协程函数,分为如下几个步骤:

1. 从堆上 (operator new) 创建 Promise 对象,保存协程的状态信息

2. initial_suspend 阶段,用于在执行协程主体  代码前做些事情

3. 阶段,执行协程的主体代码

4. unhandled_exception 阶段,若抛异常,处理异常

5. final_suspend阶段,协程结束收尾动作,在这阶段的 coroutine_handle::done 方法为 true,caller 可以通过这个方法判断协程是否结束,从而不再调用 resume 恢复协程。

而协程返回类型则是一个 Future 对象,这一步编译器通过 Promise::get_return_object() 来创建 Future 对象。而 Future 对象一般持有 Promise 的句柄:coroutine_handle,这样 caller 可以通过 Future 与 Promise 交互,从而恢复协程。

而 Promise 对象释放的时间点有两个,避免重复执行,否则会 double free:

1. final_suspend 阶段 resume 后

2. 调用 coroutine_handle::destroy() 方法

比较好的做法是在 final_suspend 阶段挂起,这时候就不可 resume 了,在 caller 通过调用 Future 持有的句柄 destroy() 方法释放 Promise 对象。综上,一个 Promise 对象需要实现如下方法:

1. initial_suspend: 返回一个 Awaitable 对象

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何写一个简单的node.js C 扩展

node 是由 c 编写的,核心的 node 模块也都是由 c 代码来实现,所以同样 node 也开放了让使用者编写 c 扩展来实现一些操作的窗口。如果大家对于 require 函数的描述还有印象的话,就会记得如果不写文件后缀,它是有一个特定的匹配规则…

在线画 有穷状态自动机 的软件_怎么画思维导图?不用下载软件,在线就能操作...

怎么画思维导图?在工作中,除了流程图,脑图也是很重要的一个存在:流程图帮助我们快速完成任务,而脑图告诉我们任务本质。画思维导图是一个积累的过程,急不来,对于新手来说还是有一定难度的。由于…

Spring Boot Actuator:在其顶部具有MVC层的自定义端点

Spring Boot Actuator端点允许您监视应用程序并与之交互。 Spring Boot包含许多内置端点,您也可以添加自己的端点。 添加自定义端点就像创建一个从org.springframework.boot.actuate.endpoint.AbstractEndpoint扩展的类一样容易。 但是Spring Boot Actuator也提供了…

422器件与lvds接收器的区别_SPI、I2C、UART三种串行总线的原理、区别

SPI、I2C、串口、我相信如果你是从事的是嵌入式开发,一定会用到这三种通信协议,串口的话因为和波特率有关,所以一般的CPU或者MCU只会配有两个或者三个串口,而数据的传输,的话SPI和I2C用得会比较多区别:1、U…

C 的 6 种内存顺序,你都知道吗?

原子操作的内存顺序有六个内存顺序选项可应用于对原子类型的操作:1. memory_order_relaxed2. memory_order_consume3. memory_order_acquire4. memory_order_release5. memory_order_acq_rel6. memory_order_seq_cst。除非你为特定的操作指定一个顺序选项&#xff0…

易语言 网页用什么编码_通常提到的编码器是干什么用的

编码器(encoder)是将信号(如比特流)或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电信号,前者成为码盘,后者称码尺.按照读出方式编码器可以分…

如何优雅地实现 C 编译期静态反射

部门请来了软件专家袁英杰咨询师指导我们软件开发,从中我也学到了很多姿势,在此记录下来宝贵的经验。苹果的 mbp 品控真是差劲,写这个东西把 LShift 键 按坏了,真是难受。反射能做什么最近和大师聊软件设计,其中一个点…

香草 jboss 工具_如何为JBoss Developer Studio 8设置BPM和规则工具

香草 jboss 工具最新的JBoss Developer Studio(JBDS)的发布带来了有关如何开始使用尚未安装的各种JBoss Integration和BPM产品工具集的问题。 在本系列文章中,我们将为您概述如何安装每套工具并说明它们支持哪些产品。 这将有助于您在着手进…

局域网steam联机_适合和基友联机一起玩的单机游戏(1)

GTA5还有什么比在GTA中,和几个好基友一起,组建帮派,联机打砸抢,组队完成任务,和其他帮派火并更有意思的呢?游戏丰富的内容,各式各样的玩法,广袤的可探索空间,不愧是史上最…

C/C assert()函数用法总结与注意事项

1. 简介assert宏的原型定义在中,其作用是如果它的条件返回错误,则终止程序执行。原型定义:#include void assert( int expression );assert的作用是先计算表达式 expression ,如果其值为假(即为0)&#xff…

ppt flash倒计时器_PPT三大神器之iSlide插件

本文约1200字,阅读预计需要4分钟。为了提升PPT制作效率,我们有必要使用一些插件来提升工作效率,而PPT有三大插件神器,分别是iSlide、PA口袋动画,Onekey Tool(俗称OK插件),今天我们就…

C 语言中std::array的神奇用法总结

std::array是在C 11标准中增加的STL容器,它的设计目的是提供与原生数组类似的功能与性能。也正因此,使得std::array有很多与其他容器不同的特殊之处,比如:std::array的元素是直接存放在实例内部,而不是在堆上分配空间&…

java线程池并发_线程池之外:Java并发并不像您想象的那样糟糕

java线程池并发Apache Hadoop,Apache Spark,Akka,Java 8流和Quasar: 针对Java开发人员的经典用例以及最新的并发方法 关于并发性更新概念的讨论很多,但是许多开发人员还没有机会将他们的想法缠住。 在本文中&#xff…

网络营销理论模型_网络营销:课堂笔记(第四章下)

网络营销产品策略(续上篇)本章知识清单三、网络品牌如何打造?什么是品牌目前为止,对品牌的含义一直没有一个统一的、权威的解释。如果从品牌的构成要素和基本功能方面来界定品牌的话,最具有代表性和最经典的表述当属美国市场营销协会的定义。…

ios多线程Android,iOS 关于多线程

一.进程和线程1.什么是进程进程是指在系统中正在运行的一个应用程序每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内比如:同时打开QQ,Xcode,系统就会分别启动2个进程通过”活动监视器”可以查看Mac系统中所开启…

websockets_使用用户名/密码和Servlet安全性保护WebSockets

websocketsRFC 6455提供了WebSockets安全注意事项的完整列表。 其中一些是在协议本身中烘焙的,其他一些则需要更多有关如何在特定服务器上实现它们的解释。 让我们来谈谈协议本身内置的一些安全性: HTTP请求中的Origin头仅包含标识发起该请求的主体&…

android横向排列 间隙,Android开发消除横向排列的多个Button之间的空隙

一.问题重述摘要里描述的可能不太清楚,问题如下图:如何消除Button1和Button2之间的空隙,以及Button与左右边界之间的空隙?二.问题根源这里出现的空隙其实是Button的背景图片中的透明部分,如下图:(两个按钮被…

电脑的发展史_互联网发展史 硅谷传奇之 IBM

2节 硅谷传奇之 IBM为什么要讲IBM呢?互联网是因计算机而诞生的,互联网的发展史与电脑的发展史有很多是重叠的,而IBM是上世纪60年代八大电脑公司之首。在互联网席卷全球之前,在硅谷是以无线电、军事技术、硅晶体管而闻名的。这些东…

android汉字田字格,画一个简单的田字格

image.png上代码package com.nick.customview;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.support.annotation.Nullable;import android.sup…

xp定时关机软件_好用又免费的电脑定时工具,不用得后悔

现在利用电脑办公的人有多少,举个手示意下!!!给电脑设置定时关机,可以方便我们不在电脑前完成关机操作。那么,如何设置定时关机呢?如果要取消,定时关机又如何取消?有的人…