浅说 c++20 cppcoro (三)
浅说 c++20 cppcoro (三),https://www.cnblogs.com/bbqzsl/p/18679860
接着上一篇浅说 c++20 coroutine (二) ,继续没说完的事。
先来看co_return 1; 的反编译代码。
再来看co_yield i; 的反编译代码。
比较它们的共通点
这里有一个技巧,co_await suspend_always{} 能够挂起当前协程,控制权返回到caller/resumer。
直到目前,我已经用了两篇作为前置铺垫。
浅说c/c++ coroutine https://www.cnblogs.com/bbqzsl/p/18639898。 介绍stackful协程的一些典型实现,用于清楚分辩stackful跟stackless的实现的差异
浅说 c++20 coroutine (二) https://www.cnblogs.com/bbqzsl/p/18659948。介绍c++20是如何实现stackless,也是cppcoro设计跟实现的基础。
接下来进入cppcoro的篇幅。
先来划分sync, async在cppcoro的意思。sync是我们通常理解的同步阻塞线程。async相对于sync则是不阻塞线程。sync wait则是阻塞线程的同步等待,例如使用多线程同步原语挂起线程等待。async wait则是不阻塞线程的异步等待,所以我们不能够挂起线程,但是我们能够挂起协程。另外异步跟回调是形影不相离般存在的,异步等待就基本是异步回调。再回到co_await。await这个关键词很让人迷惑,await跟wait是同意的,不同的是一个及物动词另一个不及物动词。现在将co_await,展开成 coro asynchronously wait后,就很好理解了。主语coro进行async wait。也就在一个coro里面async wait另一个coro。那么cppcoro实现的一些同步原语,并在名字前加上async,如async_mutex。则是特指用来coro的同步原语,coro的同步原语的“同步”有别于多线程同步原语的同步阻塞线程,coro的“同步”是不阻塞线程的并而是异步的。所以一类以async开头的事物是指用于coro的async wait。
另外,在协程之间,sync与async的关系。当一个常规函数或协程直接使用resume()恢复另一个协程,两者之间不受co_await的协议约定,caller/resumer都会在另一个协程返回控制权(如co_yield),而会继续执行后面的逻辑。它们就是sync的关系,如generator<T>就是sync产生器。如果co_await另一个协程,并且没有挂起就能够完成,返回控制权,这时它们也算是sync的关系,sync call一样。如果co_await另一个协程,协程需要挂起,根据co_await协议的约定,caller/resumer也要挂起。这时它们却是async的关系,async call并由回调恢复它们。async_generator<T>是一个async产生器,因为它支持co_await。而generator<T>是sync产生器,不支持挂起。
还有就是,coro用在所有scheduler的schedule()都算是async。这跟我们的async关键字的意思一样。例如ThreadPool.schedule()在线程池分派,io_servise.schedule()在io_service事件循环中分派。
来看cppcoro。我将cppcoro::task视作一个最小的调度单位,至于task相关的设计思路请移步上一篇。然后我再将resume()调用的地方视为调度。现在来看cppcoro代码里到底有哪些调度。
它们分别有
1. 基于执行流。task,
2. 发动器。sync_wait, async_scope, when_all
3. 基于生产-消费模式。generator, async_generator,
4. 同步原语。
single_consumer_event
single_consumer_async_auto_reset_event
async_mutex
async_manual_reset_event
async_auto_reset_event
async_latch
sequence_barrier
multi_producer_sequencer
single_producer_sequencer
5. 线程池调度器。static_thread_pool。
6. 事件循环调度器。io_service。
先看task<T>。task<T>被实现成挂起链,一旦执行流被某一个co_await的协程挂起,就会沿着co_await回溯出一条挂起链。当那个最里层挂起的协程完成时,就会在final_suspend中恢复它的caller/resumer。重复这个流层直到恢复最外层的协程。它们的角色如下,awaitingCoro co_await (which is async wait) awaiterCoro。awaiterCoro挂起,而awaitingCoro一样要挂起。awaiterCoro完成后,awaiterCoro resume awaitingCoro。
再来看,启动器。我将sync_wait,async_scope,when_all归成一个类别。我们使用它们来启动cppcoro::task<T>。task<T>被设计成lazy任务,initial_suspend()返回suspend_always。我们必须要么用co_await它,要么直接resume()它,才能让它得以继续开始。
当我们的线程还处在非协程上下文时,按照cppcoro的设计意图,我们需要将当前线程切换成协程后,才能启动其它task<T>。因为task<T>将coroutine_handle作为私有成员,我们不得直接去resume()。sync_wait()为我们生成一个sync_wait_task,并由它co_await来启动task<T>。当task<T>完成后,就会调度恢复sync_wait_task。sync_wait_task然后在final_suspend中set_event(),唤醒sync_wait()所在线程。这里有一个技巧,使用co_yield跟yield_value模拟final_suspend(),来避免task<T>的promise析构,保护结果。
当我们的线程已经运行在协程上下文时,就可以使用async_scope对象的spawn()来启动协程。再配合线程池调度器的schedule(),就能够将协程发射到线程池。这里又有一个技术点。在cppcoro的设计中,当一个协程使用调度器进行schedule()后, 关于这个协程的所有执行流都会迁移到调度器的线程(池)。作为一个启动器,我们当然不希望async_scope所在协程,也跟随启动一个协程而被迁移到其它线程。但是co_await的awaiter一定会在完成后,去恢复awaitingCoro。那么就要用一个oneway_task来隔断task<T>的调度关系。
从async_scope的实现来看,协程任务task<T>加了一层oneway_task,oneway_task的final_suspend()不同于task<T>而隔断了async_scope作为awaitingCoro被恢复。同时task<T>也走到终结销毁。可见async_scope毫不关心它启动的协程的结果。join()并不能拿到任何结果。
因此,我们需要when_all()。像async_scope一样,when_all使用when_all_task去co_await其它task<T>。并且when_all()是直接去resume所有when_all_task,而不是用co_await。隔断挂起,隔断恢复链。这样,when_all启动的协程,同样可以用线程池调度器schedule()发射到线程池,不会影响when_all()所在协程。不同于async_scope,when_all()必须在co_await操作中,才会去启动其它任务。通过是设计,async_scope.join()跟when_all()挂起等待一个counter,而不是直接去等待任务。同时when_all,使用了sync_wait一样的技法,用co_yield来保护任务结果。
虽然,由async_scope或when_all来启动的task<T>,并不会因为schedule到线程池,而连随将async_scope或when_all带到线程池,上面讲的隔断挂起的结果。但是async_scope.join()或when_all结束时,它们却都异步等待一个counter。这个counter可以认为是一个async的信号量,是一个async的同步原语。所以它们等待了一个同步原语,并由这个同步原语在某个线程将它们再调度起来。sync_wait()在结束时,阻塞线程等待(Wait)一个线程同步事件。而async_scope.join()或when_all()结束时,则异步等待(Await)一个counter(异步的信号量)。下面是简明图
下面是运行的结果:
再来看同步原语的实现,基本遵从async原则,不阻塞线程,只挂起协程,协程挂起时入链到同步原语的挂起链。当同步原语发起事件时,将挂起链的所有协程恢复。co_await一个同步原语变量, 挂起并异步等待恢复。因为恢复时属于异步回调,所以挂起协程会被调度到同步原语发起事件的线程。
再来看线程池static_thread_pool的调度实现。
再来看io_service是调度实现。
因此io_service可以用作成一个strand串行分派协程任务跟io异步操作。
总的来说,一个cppcoro task<T>在执行过程中挂起时都有一个因缘(,或者挂起源),它可以是另一个task<T>,或者是cppcoro同步原语,线程池,io_service等,并由它们异步调度(恢复回调)。
本篇结束。
浅说 c++20 cppcoro (三)的更多相关文章
- 20面向对象三特征 之继承 方法重写 super
继承是:多个类有重复内容,把重复内容放到一个新类中,就可以通过extends关键词去让原来的类和新类产生继承关系,子类只能拿到父类一部分信息.通过extends关键词去指明类与类之间的关系,一个父类可 ...
- 最新Internet Download Manager (IDMan) 6.25 Build 20 32位 64位注册破解补丁
0x00 IDMan介绍 Internet Download Manager提升你的下载速度最多达5倍,安排下载时程,或续传一半的软件.Internet Download Manager的续传功能可以 ...
- JavaScript进阶(三)
现在来说说判断语句(if)if语句是基于条件成立才执行相应代码时使用的语句.语法:if(条件){条件成立时执行代码}注意:if小写,大写字母(IF)会出错!假设你应聘web前端技术开发岗位,如果你会h ...
- 20172328 2018-2019《Java软件结构与数据结构》第三周学习总结
20172328 2018-2019<Java软件结构与数据结构>第三周学习总结 概述 Generalization 本周学习了第五章:队列.主要内容包含队列的处理过程.如何用对例如求解问 ...
- 【Python3练习题 020】 求1+2!+3!+...+20!的和
方法一 import functools sum = 0 for i in range(1,21): sum = sum + functools.reduce(lambda x,y: x* ...
- mysql-5.5.20预编译安装
1.MYSQL数据库概念 1)MYSQL是一款关系型数据库系统,数据之间有互相联系,互相的关联和调用的. 2)MYSQL数据用于存储:WEB网站用户名和密码等 3)MYSQL存储数据库是通过二维表格形 ...
- 20172325 2018-2019-2 《Java程序设计》第三周学习总结
20172325 2018-2019-2 <Java程序设计>第三周学习总结 教材学习内容总结 一.什么是队列? 队列是一种线性集合,其元素从一端加入,从另一端删除: 队列的元素按照FIF ...
- 20155220 2016-2017-2《java程序设计》第三周学习总结
教材学习内容总结 1.注意java与c语言的区别,在java中,数组是一个对象. 2.了解java.util.Scanner和java.math.BigDecimal这两个标准类 3.System.a ...
- 20155230 2016-2017-2 《Java程序设计》第三周学习总结
---恢复内容开始--- 20155230 张瑞琦 2016-2017-2 <Java程序设计>第三周学习总结 教材学习内容总结 1.使用浮点数时用equals()进行比较,否则会出错. ...
- 《Think in Java》20 21(并发)
chapter 20 注解 三种标准注解和四种元注解: 编写注解处理器 chapter 21 并发 基本的线程机制 定义任务 package cn.test; public class LiftOff ...
随机推荐
- python3安装与使用(Linux)
之前写过有关Windows下的python3的安装与使用,这次看一下Linux下的python3 1. 安装依赖环境 yum -y install zlib-devel bzip2-devel ope ...
- Blazor 组件库 BootstrapBlazor 中Circle组件介绍
组件介绍 Circle进度环组件,是一个图表类组件.一般有两种用途: 显示某项任务进度的百分比. 统计某些指标的占比. 它的样子如下: 它的代码如下: <Circle Width="2 ...
- 腾讯技术岗位笔试&面试题(五)
说在前面 本篇文章是腾讯技术面试题目汇总第五篇. 后续将持续推出互联网大厂,如阿里,腾讯,百度,美团,头条等技术面试题目,以及答案和分析. 欢迎大家点赞关注转发. 1.define.const.typ ...
- tomcat部署cas6并配置自己的ssl证书
配置并安装tomcat,详见我的文章:windows安装tomcat10 安装必备的软件:(在<windows安装tomcat10>中已详细配置) apache-tomcat-10.1. ...
- CentOS7.8安装k8s
1, 安装 docker / kubelet # 在 master 节点和 worker 节点都要执行 \# 最后一个参数 1.20.6 用于指定 kubenetes 版本,支持所有 1.20.x 版 ...
- 数据万象推出智能检索MetaInsight,现已开启限时公测
海量文件的分析统计一直是对象存储COS的热点需求,伴随AIGC飞速迭代发展,在众多不同模态素材的海洋中,用户也急需更高效地管理和利用多媒体内容,打破传统搜索的桎梏. 数据万象推出的智能检索 MetaI ...
- 在app內建web server
这几年在三家企业都使用 app 內建 web server 的技术方案.效果很好. 该方案顾名思义,就是在 app 中加入一个 embed webserver 组件.组件和app运行于同一进程空间.程 ...
- Redis应用—9.简单应用汇总
大纲 1.基于Redis实现的简单缓存机制(String数据结构) 2.实现一个最简单的分布式锁(String数据结构) 3.博客网站的文章发布与查看(String数据结构) 4.博客字数统计与文章预 ...
- 更改linux文件/目录的权限、拥有者及用户组
在Linux中,创建一个文件时,该文件的拥有者都是创建该文件的用户.该文件用户可以修改该文件的拥有者及用户组,root用户可以修改任何文件的拥有者及用户组. 在Linux中,对于文件的权限(rwx), ...
- 解决httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0
httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for Se ...