协程Part1-boost.Coroutine.md
首先,在计算机科学中 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 的区间内),所以能够正常调用递归函数。相反,有栈协程调用递归函数的时候,所使用的栈是该协程所申请的栈。
分三个方面来总结的话就是:
内存资源使用:无栈协程借助函数的栈帧来存储一些寄存器状态,可以调用递归函数。而有栈协程会要申请一个内存栈用来存储寄存器信息,调用递归函数可能会爆栈。
速度:无栈协程的上下文比较少,所以能够进行更快的用户态上下文切换。
功能性:有栈协程能够在嵌套的协程中进行挂起/恢复,而无栈协程只能对顶层的协程进行挂起,被调用方是不能挂起的。
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::begin
和std::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_type
和push_type
都会变成完成状态,也就是pull_type::operator bool()
和push_type::operator bool()
都会变成 false
;
协程的异常处理
coroutine函数内的代码不能阻止 unwind 的异常,不然会 stack-unwinding失败。
stack unwinding 通常和异常处理一起讨论,当异常抛出的时候,执行权限会立即向上传递直到任意一层 catch 住抛出的异常,而在向上传递前,需要适当地回收、析构本地自动变量,如果一个自动变量在异常抛出的时候被合适地被释放了就可以称为"unwound"了。
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
)了,但是可以从历史的角度来理解协程的分类和基本工作原理,为现在多样化的协程探索拓宽道路。
协程Part1-boost.Coroutine.md的更多相关文章
- Lua的协程(coroutine)
-------------------------------------------------------------------------------- -- 不携带参数 ---------- ...
- coroutine协程
如果你接触过lua这种小巧的脚本语言,你就会经常接触到一个叫做协程的神奇概念.大多数脚本语言都有对协程不同程度的支持.但是大多编译语言,如C/C++,根本就不知道这样的东西存在.当然也很多人研究如何在 ...
- Coroutine(协程)模式与线程
概念 协程(Coroutine)这个概念最早是Melvin Conway在1963年提出的,是并发运算中的概念,指两个子过程通过相互协作完成某个任务,用它可以实现协作式多任务,协程(coroutine ...
- 云风协程库coroutine源码分析
前言 前段时间研读云风的coroutine库,为了加深印象,做个简单的笔记.不愧是大神,云风只用200行的C代码就实现了一个最简单的协程,代码风格精简,非常适合用来理解协程和用来提升编码能力. 协程简 ...
- 实现一个简单的C++协程库
之前看协程相关的东西时,曾一念而过想着怎么自己来实现一个给 C++ 用,但在保存现场恢复现场之类的细节上被自己的想法吓住,也没有深入去研究,后面一丢开就忘了.近来微博上看人在讨论怎么实现一个 user ...
- 浅谈Go语言的Goroutine和协程
0x00.前言 前面写了一篇初识Go语言和大家一起学习了Go语言的巨大潜力.语言简史.杀手锏特性等,感兴趣的读者可以回顾一下. 今天来学习Go语言的Goroutine机制,这也可能是Go语言最为吸引人 ...
- Python PEP 492 中文翻译——协程与async/await语法
原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...
- Lua学习笔记(六):协程
多线程和协程 多线程是抢占式多任务(preemptive multitasking),每个子线程由操作系统来决定何时执行,由于执行时间不可预知所以多线程需要使用同步技术来避免某些问题.在单核计算机中, ...
- Lua 5.3 协程简单示例
Lua 5.3 协程简单示例 来源 http://blog.csdn.net/vermilliontear/article/details/50547852 生产者->过滤器->消费者 模 ...
- Golang源码探索(二) 协程的实现原理
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻 ...
随机推荐
- Java 异步编程 (5 种异步实现方式详解)
同步操作如果遇到一个耗时的方法,需要阻塞等待,那么我们有没有办法解决呢?让它异步执行,下面我会详解异步及实现@mikechen 目录 什么是异步? 一.线程异步 二.Future异步 三.Comp ...
- 使用Docker方式部署Gitlab,Gitlab-Runner并使用Gitlab提供的CI/CD功能自动化构建SpringBoot项目
1.Docker安装Gitlab,地址:https://www.cnblogs.com/sanduzxcvbnm/p/13814730.html 2.Docker安装Gitlab-runner,地址: ...
- 使用Elasticsearch中的copy_to来提高搜索效率
在今天的这个教程中,我们来着重讲解一下如何使用Elasticsearch中的copy来提高搜索的效率.比如在我们的搜索中,经常我们会遇到如下的文档: { "user" : &quo ...
- 第一个Django应用 - 第五部分:测试
一.自动化测试概述 什么是自动化测试 测试是一种例行的.不可缺失的工作,用于检查你的程序是否符合预期. 测试可以划分为不同的级别.一些测试可能专注于小细节(比如某一个模型的方法是否会返回预期的值?), ...
- Loki日志系统基础知识
文章摘抄转载自:https://lluozh.blog.csdn.net/article/details/111027998 Loki 日志系统由以下3个部分组成: loki是主服务器,负责存储日志和 ...
- PAT (Basic Level) Practice 1013 数素数 分数 20
令 Pi 表示第 i 个素数.现任给两个正整数 M≤N≤104,请输出 PM 到 PN 的所有素数. 输入格式: 输入在一行中给出 M 和 N,其间以空格分隔. 输出格式: 输出从 PM 到 ...
- JDBC连接SQL Server2008 完成增加、删除、查询、修改等基本信息基本格式及示例代码
连接数据库的步骤: 1.注册驱动 (只做一次) 2.建立连接 3.创建执行SQL的语句.执行语句 4.处理执行结果 5.释放资源 1.建立连接的方法: Class.forName("com. ...
- Linux系统安装宝塔面板教程
# Linux系统宝塔安装教程 注意:安装宝塔面板的前提条件 首先要有一台服务器或者使用linux系统的虚拟机. 安装前请确保是[全新的机器].必须是没装过其它环境的新系统,如Apache/Nginx ...
- 深入理解独占锁ReentrantLock类锁
ReentrantLock介绍 [1]ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程 ...
- Redis Cluster 原理说的头头是道,这些配置不懂就是纸上谈兵
Redis Cluster 原理说的头头是道,这些配置不懂就是纸上谈兵 Redis Cluster 集群相关配置,使用集群方式的你必须重视和知晓.别嘴上原理说的头头是道,而集群有哪些配置?如何配置让集 ...