协程Part1-boost.Coroutine.md

news2024/11/25 20:46:18

首先,在计算机科学中 routine 被定义为一系列的操作,多个 routine 的执行形成一个父子关系,并且子 routine 一定会在父 routine 结束前结束,也就是一个个的函数执行和嵌套执行形成了父子关系。

coroutine 也是广义上的 routine,不同的是 coroutine 能够通过一些操作保持执行状态,显式地挂起和恢复,相对于 routine 的单控制流,coroutine 能提供一个加强版的控制流。


协程执行转移

如图中的处理流程,多个 coroutine 通过一些机制,首先执行 routine foo 上的 std::cout << "a" 然后切换到 routine bar 上执行 std::cout << "b",再切换回 routine foo 直到两个 routine 都执行完成。

coroutine 如何运行?

通常每个 corotuine 都有自己的 stack 和 control-block,类似于线程有自己的线程栈和control-block,当协程触发切换的时候,当前 coroutine 所有的非易失(non-volatile)寄存器都会存储到 control-block 中,新的 coroutine 需要从自己相关联的 control-block 中恢复。

协程的分类

A. 根据协程的执行转移机制可以分为非对称协程和对程协程:

  • 非对称协程能知道其调用方,调用一些方法能让出当前的控制回到调用方手上。
  • 对程协程都是平等的,一个对程协程能把控制让给任意一个协程,因此,当对称协程让出控制的时候,必须指定被让出的协程是哪一个。

B. 根据运行时协程栈的分配方式又能分为有栈协程和无栈协程:

通常情况下,有栈协程比无栈协程的功能更加强大,但是无栈协程有更高的效率,除此之外还有下面这些区别:

有栈协程能够在嵌套的栈帧中挂起并且在之前嵌套的挂起点恢复,而无栈协程只有最外层的 coroutine 才能够挂起,由顶层 routine 调用的 sub-routine 是不能够被挂起的。

有栈协程通常需要分配一个确定且固定的内存用来适配 runtime-stack,上下文的切换的时候相比于无栈协程也更加消耗资源,比如无栈协程仅仅只需要存储一个程序计数器(EIP)。有栈协程在语言(编译器)的支持下,有栈协程能够利用编译期计算得到非递归协程栈的最大大小,因此,内存的使用方面能够有所优化。无栈协程,不是代表没有运行时的栈,无栈只是代表着无栈协程所使用的栈是当前所在上下文的栈(比如一个函数 ESP~EBP 的区间内),所以能够正常调用递归函数。相反,有栈协程调用递归函数的时候,所使用的栈是该协程所申请的栈。

分三个方面来总结的话就是:

  1. 内存资源使用:无栈协程借助函数的栈帧来存储一些寄存器状态,可以调用递归函数。而有栈协程会要申请一个内存栈用来存储寄存器信息,调用递归函数可能会爆栈。

  2. 速度:无栈协程的上下文比较少,所以能够进行更快的用户态上下文切换。

  3. 功能性:有栈协程能够在嵌套的协程中进行挂起/恢复,而无栈协程只能对顶层的协程进行挂起,被调用方是不能挂起的。

Boost.Coroutine

C++ Boost 库在 2009 年就提供了一个子库叫做 Boost.Coroutine 实现了有栈协程,且实现了对称(symmetric)和非对程(symmetric)协程。

1. 非对程协程(Asymmetric coroutine)

非对程协程提供了 asymmetric_coroutine<T>::push_type 和 asymmetric_coroutine<T>::pull_type 两种类型用于处理协程的协作。由命名可以理解,非对程协程像是创建了一个管道,通过push_type写入数据,通过pull_type拉取数据。

协程例子 A

boost::coroutines::asymmetric_coroutine<int>::pull_type source(
    [&](boost::coroutines::asymmetric_coroutine<int>::push_type& sink){
        int first=1,second=1;
        sink(first);
        sink(second);
        for(int i=0;i<8;++i){
            int third=first+second;
            first=second;
            second=third;
            sink(third);
        }
    });

for(auto i : source)
    std::cout << i <<  " ";

output:
1 1 2 3 5 8 13 21 34 55

上面的例子是协程实现的斐波那契数列计算,在上面的例子中,push_type 的实例构造时接受了一个函数作为构造函数入参,而这个函数就是 协程函数(coroutine function),coroutine 在 pull_type 创建的上下文下运行。

该协程函数的入参是一个以 push_type&,当实例化外层上下文中 pull_type 的时候,Boost 库会自动合成一个 push_type传递给协程函数使用,每当调用 asymmetric_coroutine<>::push_type::operator() 的时候,协程会重新把控制权交还给push_type所在的上下文。其中asymmetric_coroutine<T> 的模板参数 T 定义了协程协作时使用的数据类型。

由于 pull_type 提供了input iterator,重载了 std::beginstd::end所以能够用 range-based for 循环方式来输出结果。

另外要注意的是,当第一次实例化pull_type的时候,控制权就会转移到协程上,执行协程函数,就好比要拉取(pull)数据需要有数据先写入(push)。

协程例子 B

struct FinalEOL{
    ~FinalEOL(){
        std::cout << std::endl;
    }
};

const int num=5, width=15;
boost::coroutines::asymmetric_coroutine<std::string>::push_type writer(
    [&](boost::coroutines::asymmetric_coroutine<std::string>::pull_type& in){
        // finish the last line when we leave by whatever means
        FinalEOL eol;
        // pull values from upstream, lay them out 'num' to a line
        for (;;){
            for(int i=0;i<num;++i){
                // when we exhaust the input, stop
                if(!in) return;
                std::cout << std::setw(width) << in.get();
                // now that we've handled this item, advance to next
                in();
            }
            // after 'num' items, line break
            std::cout << std::endl;
        }
    });

std::vector<std::string> words{
    "peas", "porridge", "hot", "peas",
    "porridge", "cold", "peas", "porridge",
    "in", "the", "pot", "nine",
    "days", "old" };

std::copy(boost::begin(words),boost::end(words),boost::begin(writer));

output:
           peas       porridge            hot           peas       porridge
           cold           peas       porridge             in            the
            pot           nine           days            old

接下来的这个例子主要说明了控制的反转,通过在主上下文中实例化的类型是push_type,逐个传递一系列字符串给到协程函数完成格式化输出,其构造函数是以pull_type&作为入参的匿名函数,在实例化push_type的过程中,库仍然会合成一个pull_type传递给该匿名函数,也就是协程函数。

与实例化pull_type不同,在主上下文中实例化push_type并不会直接进入到协程函数中,而是需要调用push_type::operator() 才能切换到协程上。

asymmetric_coroutine<T> 的模板参数 T 的类型不是 void 的时候,在协程函数中,可以通过pull_type::get()来获取数据,并通过pull_type::bool()判断协程传递的数据是否合法。

协程函数会以一个简单的return语句回到调用方的routine上,此时pull_typepush_type都会变成完成状态,也就是pull_type::operator bool()push_type::operator bool() 都会变成 false;

协程的异常处理

coroutine函数内的代码不能阻止 unwind 的异常,不然会 stack-unwinding失败。

stack unwinding 通常和异常处理一起讨论,当异常抛出的时候,执行权限会立即向上传递直到任意一层 catch 住抛出的异常,而在向上传递前,需要适当地回收、析构本地自动变量,如果一个自动变量在异常抛出的时候被合适地被释放了就可以称为"unwound"了。

stackoverflow: what is stack unwinding?

try {
    // code that might throw
} catch(const boost::coroutines::detail::forced_unwind&) {
    throw;
} catch(...) {
    // possibly not re-throw pending exception
}

在 coroutine 内部捕获到了 detail::forced_unwind 异常时要继续抛出异常,否则会 stack-unwinding 失败,另外在 push_type 和 pull_type 的构造参数 attribute 也控制是是否需要 stack-unwinding。

2. 对称协程(Symmetric coroutine)

相对于非对称协程来说,对称协程能够转移执行控制给任意对称协程

std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b)
{
    std::vector<int> c;
    std::size_t idx_a=0,idx_b=0;
    boost::coroutines::symmetric_coroutine<void>::call_type* other_a=0,* other_b=0;

    boost::coroutines::symmetric_coroutine<void>::call_type coro_a(
        [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
            while(idx_a<a.size())
            {
                if(b[idx_b]<a[idx_a])    // test if element in array b is less than in array a
                    yield(*other_b);     // yield to coroutine coro_b
                c.push_back(a[idx_a++]); // add element to final array
            }
            // add remaining elements of array b
            while ( idx_b < b.size())
                c.push_back( b[idx_b++]);
        });

    boost::coroutines::symmetric_coroutine<void>::call_type coro_b(
        [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
            while(idx_b<b.size())
            {
                if (a[idx_a]<b[idx_b])   // test if element in array a is less than in array b
                    yield(*other_a);     // yield to coroutine coro_a
                c.push_back(b[idx_b++]); // add element to final array
            }
            // add remaining elements of array a
            while ( idx_a < a.size())
                c.push_back( a[idx_a++]);
        });


    other_a = & coro_a;
    other_b = & coro_b;

    coro_a(); // enter coroutine-fn of coro_a

    return c;
}

std::vector< int > a = {1,5,6,10};
std::vector< int > b = {2,4,7,8,9,13};
std::vector< int > c = merge(a,b);
print(a);
print(b);
print(c);

output:
    a : 1 5 6 10
    b : 2 4 7 8 9 13
    c : 1 2 4 5 6 7 8 9 10 13

上面的例子是使用对称协程实现的一个有序数组的合并,对称协程提供了相类似的symmetric_coroutine<>::call_type 和 symmetric_coroutine<>::yield_type 两种类型用于对称协程的协作。call_type 在实例化的时候,需要接受一个以yield_type& 作为参数的(协程)函数进行构造,Boost库会自动合成一个yield_type作为实参进行传递,并且实例化 call_type 的时候,不会转移控制到协程函数上,而是在第一次调用call_type::operator()的时候才会进入到协程内。

yield_type::operator() 的调用需要提供两个参数,分别是需要转移控制的协程需要传递的值,如果 symmetric_coroutine<T> 的模板参数类型是 void,那么不需要提供值,只是简单的转移控制。

在异常处理和退出方面,对称协程和非对称协程基本一致,非对程提供了一种多协程协作方案。

结语

虽然 Boost.Coroutine 库已经被标记为标记为已过时(deprecated)了,但是可以从历史的角度来理解协程的分类和基本工作原理,为现在多样化的协程探索拓宽道路。

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

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

相关文章

网页JS自动化脚本(五)修改文字元素的内容和大小

今天的网页打开全是灰色的,顺便缅怀一下伟人,那么我我们今天定位换成按钮文字 window.onloadfunction(){var theElementdocument.querySelector("input[typesubmit]");theElement.value"爱我中华";theElement.style"font-size:25px"; }这一次的…

提分必练!中创教育PMP全真模拟题分享来喽

湖南中创教育每日五题分享来啦&#xff0c;“日日行&#xff0c;不怕千万里&#xff1b;常常做&#xff0c;不怕千万事。”&#xff0c;每日五题我们练起来&#xff01; 1、一个项目正在实行敏捷方法&#xff0c;在迭代过程中&#xff0c;团队成员互相合作&#xff0c;解决了一…

【机器学习】核函数

核方法 核技巧 非线性分类问题是指通过利用非线性模型才能很好地进行分类的问题。如图 111 所示&#xff0c;“●”表示正样本&#xff0c;“”表示负样本&#xff0c;显然无法用直线&#xff08;线性模型&#xff09;将正负样本正确分开&#xff0c;但是可以用一条椭圆曲线&…

记一次大事务优化历程(短信发送)

问题背景 短信服务数据库连接数告警&#xff0c;grafana查看数据库连接池被打满。 问题分析 在这段时间内&#xff0c;通过链路分析&#xff0c;发现最终调用第三方短信发送服务偶然耗时过长&#xff0c;分析了原有发送逻辑的代码&#xff0c;该实现在入口send处加了事务&am…

leetcode4. 寻找两个正序数组的中位数python_二分查找和递归(困难)

题目 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为 O(log (mn)) 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,3], nums2 [2] 输出&#xff1a;2.00000 解释…

第二证券|疫情扰动叠加需求不足,11月制造业PMI回落至48%

国家统计局周三称&#xff0c;11月&#xff0c;受国内疫情点多面广频发&#xff0c;世界环境更趋复杂严峻等多重要素影响&#xff0c;我国制造业收购经理人指数&#xff08;PMI&#xff09;较上月回落1.2个百分点至48.0%。制造业PMI接连两个月低于临界点&#xff0c;制造业下行…

第4季2:并口、MIPI、LVDS的简介

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、并口的简介 1、并口的含义 并口的含义&#xff0c;可以从AR0130或OV9712的原理图中形象地理解。 如下图所示&#xff0c;AR0130采用12bit的并口向SoC传输图像数据信息&#xff0c;而SoC和AR0130…

b站黑马JavaScript的Ajax案例代码——评论列表案例

目标效果&#xff1a; 1.在表单界面输入评论人和内容&#xff0c;点击发表评论按钮&#xff0c;可以在页面下面看到自己刚刚输入的内容 2.发表评论成功之后&#xff0c;用DOM对象的reset方法&#xff1a;重置表单为其默认值 e.g.1初始状态&#xff1a;【下面的评论内容会因为…

STC 51单片机48——数码管显示外部中断次数

#include<reg52.h> #include<intrins.h> #include "math.h" #define uchar unsigned char #define uint unsigned int #define ulong unsigned long //共阴字形码表【实验】数码管实验时&#xff0c;一定要将点阵模块跳针放到VCC上&#xff01;&…

【C语言】哈夫曼树,再来一次解剖

博主&#xff1a;&#x1f44d;不许代码码上红 欢迎&#xff1a;&#x1f40b;点赞、收藏、关注、评论。 格言&#xff1a; 大鹏一日同风起&#xff0c;扶摇直上九万里。 文章目录一、定义结构1.1 定义结点权值的数据类型1.2 定义单个结点信息1.3 字符指针数组中存储的元素类…

C++ Reference: Standard C++ Library reference: Containers: list: list: list

C官网参考链接&#xff1a;https://cplusplus.com/reference/list/list/list/ 公有成员函数 <list> std::list::list C98 默认构造函数 (1) explicit list (const allocator_type& alloc allocator_type()); 填充构造函数 (2) explicit list (size_type n,…

将整个网站变为黑白色

目录 效果&#xff1a; 代码&#xff1a; 兼容性写法&#xff1a; 原理&#xff1a; 效果&#xff1a; ps&#xff1a;实测淘宝也是用的这种方式&#xff0c;有兴趣可以去看看 代码&#xff1a; 使用方式就是找到根标签&#xff0c;将里面的两行代码放进去即可 html {filte…

手写Redux(二):实现React-redux

在React中&#xff0c;组件和组件之间通过props传递数据的规范&#xff0c;极大地增强了组件之间的耦合性&#xff0c;而context类似全局变量一样&#xff0c;里面的数据能被随意接触就能被随意修改&#xff0c;每个组件都能够改context里面的内容会导致程序的运行不可预料。 …

借助cubeMX实现STM32MP157A(-M4核)UART、按键中断、环境检测开关实验

main.c 可以添加一句打印提示 int main(void) {/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init(…

准备蓝桥杯的宝贝们,二分法基础篇(下)例题讲解

二分法例题第一题&#xff1a;搜索插入位置解法一&#xff08;左闭右闭&#xff09;解法二&#xff08;左闭右开&#xff09;解法三&#xff08;暴力求解&#xff09;第二题&#xff1a;在排序数组中查找元素的第一个和最后一个位置解法一&#xff08;左闭右闭&#xff09;第三…

​Base64编码知识详解 ​

在我们进行前端开发时&#xff0c;针对项目优化&#xff0c;常会提到一条&#xff1a;针对较小图片&#xff0c;合理使用Base64字符串替换内嵌&#xff0c;可以减少页面http请求。 并且还会特别强调下&#xff0c;必须是小图片&#xff0c;大小不要超过多少KB&#xff0c;等等。…

Flume监听多个文件目录,并根据文件名称不同,输出到kafka不同topic中

一、Flume监听多个文件目录 1. flume的环境搭建和基础配置参考 https://blog.csdn.net/qinqinde123/article/details/128130131 2. 修改配置文件flume-conf.properties #定义两个是数据源source1、source2 agent.sources source1 source2 agent.channels channel1 agent.…

B. Password(KMP)

Problem - 126B - Codeforces Asterix、Obelix和他们的临时伙伴Suffix和Prefix终于找到了和谐寺。然而&#xff0c;它的门被牢牢地锁住了&#xff0c;即使是Obelix也没能打开它们。 过了一会儿&#xff0c;他们发现在寺庙大门下面的一块岩石上刻着一个字符串。亚力认为那是打开…

realme手机配什么蓝牙耳机?realme蓝牙耳机推荐

蓝牙耳机作为人手必备的单品&#xff0c;不同厂商的产品更是多种多样&#xff0c;用户可以有更多的选择&#xff0c;选购蓝牙耳机的时候&#xff0c;除了看重佩戴舒适度、发声单元人们更加追求最新研发的技术。realme是为年轻人而来的科技潮牌。秉持“敢越级”品牌理念&#xf…

iOS MD5基础知识

MD5信息摘要算法&#xff08;英语&#xff1a;MD5 Message-Digest Algorithm&#xff09;&#xff0c;一种被广泛使用的密码散列函数&#xff0c;可以产生出一个128位&#xff08;16字节&#xff09;的散列值&#xff08;hash value&#xff09;&#xff0c;用于确保信息传输完…