博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

协程

Posted on 2017-08-08 18:46  bw_0927  阅读(268)  评论(0)    收藏  举报

http://www.cnblogs.com/my_life/articles/5452297.html

http://www.cnblogs.com/my_life/articles/5026368.html

 

coroutine(协程)和subroutine(子例程、函数)的不同点在于coroutine可以很多个调用入口点,而subroutine只能有一个。当coroutine被第一次调用到的时候,它将从起始处开始执行,一旦遇到具有yield(类似subroutine中的return语句)语义的语句的时候,就是返回给另外一个coroutine或者调用者,而在接下来再次被调用的时候,就从yield语句下面的一条语句继续执行直到遇到新的yield语句或者coroutine的结束。

协程之间可以通过 yield 方式转移执行权,对称(symmetric)、平级地调用对方,而不是像例程那样上下级调用关系。当然 Knuth 的“特例”指的是协程也可以模拟例程那样实现上下级调用关系,这就叫非对称协程(asymmetric coroutines)。

 

http://www.cnblogs.com/my_life/articles/5942395.html

其实现使用的Boost.Context来进行上下文的切换。使用需要包含头文件,位于名字空间boost::coroutines。
  其实现原理是每个协程都有自己的stack和control-block(boost::contexts::fcontext_t),在协程需要暂停的时候,当前协程的所有非易失的寄存器(包括ESP、EIP)都会被保存在control-block当中,而新激活的协程会从其相关的control-block中加载回复相关的寄存器信息,称之为上下文切换,相关的上下文切换不需要系统特权。
  Boost.Context提供的协程包括两类:非对称型协程asymmetric_coroutine的和对称型协程symmetric_coroutine,前者协程知道唤醒自己的协程,当需要暂停的时候控制流转换给那个特定的协程;

对称协程中所有的协程都是相等的,协程可以把控制流给任何一个其它的协程。

所以对称协程主要是表达并发编程中的多个独立的执行单元,而非对称协程常常用于对数据进行顺序处理的过程。
  stackful协程可以从嵌套的stackframe中暂停执行,在恢复的时候可以在其暂停的地方继续执行,

stackless协程只有顶层的函数(top-level routine)可以被暂停,所有顶层函数调用的函数【顶层函数的子函数】都不允许被暂停,也就是不允许嵌套使用携程。
  

stackful的协程可以被嵌套使用,但是要求协程是可移动(move)但不可拷贝(copy)的,因为其为RAII实现的,结束的时候资源会被自动清理释放,但是拷贝会导致内部的状态不可控。

同时使用时候在context_switch切换到正在执行的相同协程的行为是未定义的。

 

http://blog.csdn.net/wuhenyouyuyouyu/article/details/52709395

被广为引用的协程定义:

  • 协程的本地数据在后续调用中始终保持
  • 协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行

 

 

 1 var q := new queue
 2 
 3 coroutine produce
 4     loop
 5         while q is not full
 6             create some new items
 7             add the items to q
 8         yield to consume
 9 
10 coroutine consume
11     loop
12         while q is not empty
13             remove some items from q
14             use the items
15         yield to produce

这个例子中容易让人产生疑惑的一点就是yield的使用,它与我们通常所见的yield指令不同。这是因为我们常见的yield指令大都是基于生成器(Generator)这一概念的。下面是基于生成器的生产-消费者模型实现(依然来自Wiki):  

1 var q := new queue
 2 
 3 generator produce
 4     loop
 5         while q is not full
 6             create some new items
 7             add the items to q
 8         yield consume
 9 
10 generator consume
11     loop
12         while q is not empty
13             remove some items from q
14             use the items
15         yield produce
16 
17 subroutine dispatcher
18     var d := new dictionary (generator → iterator)
19     d[produce] := start produce
20     d[consume] := start consume
21     var current := produce
22     loop
23         current := next d[current] 

  

协程的分类

依照三个问题区分协程:

  • 控制传递(Control-transfer)机制
  • 协程是否作为语言的第一类(First-class)对象提供
  • 协程是否为栈式(Stackful)构造,即是否可以在内部的嵌套调用中挂起

对称与非对称协程

控制传递机制的不同区分出了对称(Symmetric)和非对称(Asymmetric)协程

对称协程只提供一种传递操作,用于在协程间直接传递控制。【可以控制当前协程挂起后,下一步由哪个协程来继续执行

非对称协程(常称为半对称(Semi-symmetric)协程或半(Semi)协程)提供调用和挂起两种操作,挂起时控制返回给调用者【当前线程挂起后,控制权只能返回给调用者】

在我们的生产-消费者模型的例子中,前者是对称协程,生成器是一种非对称协程。

 

出于支持并发而提供的协程通常是对称协程【控制权可以直接传递】,用于表示独立的执行单元,如Modula-2中的协程。

用于产生值序列的协程则为非对称协程【控制权只能返回给调用者】,如迭代器和生成器。

 

在很长一段时间里的普遍看法是,对称与非对称协程的能力不同。所以一些支持通用协程机制的语言同时提供了这两类控制传递,如Simula和BCPL。

事实上很容易证明这两种控制传递机制可以相互表达,因此要提供通用协程时只须实现其中一种即可。但是,两者表达力相同并不意味着在易用性上也相同。

对称协程会把程序的控制流变得复杂而难以理解和管理,而非对称协程的行为在某种意义上与函数类似,因为控制总是返回给调用者。

使用非对称协程写出的程序更加结构化。

第一类(First-class)与受限协程

协程是否作为语言的第一类对象提供对表达力的影响极大。为特定用途而实现的协程,往往把协程对象限制在 指定的代码结构中,无法由程序员直接控制。一些语言实现的迭代器(CLU,Sather)和生成器(Icon)被限制在某个循环内使用,属于受限协程。只有实现为第一类对象的协程可以提供自定义控制结构的能力,而这种能力正是协程强大的表现力所在。

栈式(Stackful)构造

栈式协程允许在内部的嵌套函数中挂起,恢复时从挂起点继续执行

非栈式协程只能在主体部分执行挂起操作,可以用来开发简单的迭代器或生成器,但遇到复杂些的控制结构时,会把问题搞得更加复杂

例如,如果生成器的生成项是通过递归或辅助函数生成的,必须创建出一系列相应层级结构的辅助生成器连续生成项直到到达原始调用点。

非栈式协程也不足以实现用户级多任务。

 

完全协程

综上所述可以认为,是否为第一类对象以及是否为栈式构造,这两个问题决定了协程的能力。Revisiting Coroutines一文提出了完全协程的概念,即第一类、栈式的协程对象

随后论证了完全协程的表达力等同于One-shot continuation,关于Continuation的概念及相关论证在随后的文章中我会提到,Continuation的出现也一定程度上导致了对协程研究的中止,因为普遍认为Continuation的表达力要远超协程。

如今对协程的研究和应用有重新复苏的趋势,主要集中在两个方向。一个是研究它在协作式多任务管理上相对于多线程的优势,目前以程序库和系统资源的方式提供了一些此类协程。另一个就是用于迭代器和生成器的协程,如Perl、C#、Python等。而Lua基于Revisiting Coroutines的观点,实现了完全非对称协,事实也证明了这种机制在实现一些控制结构时异常方便和强大。

 

http://blog.csdn.net/wuhenyouyuyouyu/article/details/52709450

协程(二):协程的应用

 

总的来说,完全协程才算得上是真正意义上的协程,其它如生成器等只是部分实现了协程概念的非完全协程,我们之后主要讨论完全协程。

本篇介绍一些协程的实际应用。协程本质是一种控制抽象,它的价值在于可以简洁优雅地实现一些控制行为。在协程中,控制可以从当前执行上下文跳转到程序的其它位置,并且可以在之后的任意时刻恢复当前执行上下文,控制从跳出点处继续执行。这种行为与Continuation类似,但协程相比之下更容易理解,某些情况下还更加高效。之后会有一篇专门对比这两个概念。

http://blog.csdn.net/wuhenyouyuyouyu/article/details/52709475

协程(三)协程与Continuation

 

出处:http://simple-is-better.com/news/697

 

Continuation表示一个运算在其指定位置的剩余部分。当Continuation作为语言的第一类(First-class)对象时,可用于实现多种控制结构。同样作为控制结构,First-class continuation的表达力比协程更加强大,而且有着明确定义的语义,以至于在它出现之后对协程的研究就几乎完全停止了。但后来Revisiting Coroutines中证明了完全协程与One-shot continuation的表达力是完全相同的,而且协程更容易理解和使用,在某些情况下也更加高效

理解Continuation

Continuation是一种描述程序的控制状态的抽象,它用一个数据结构来表示一个执行到指定位置的计算过程;这个数据结构可以由程序语言访问而不是隐藏在运行时环境中。Continuation在生成之后可作为控制结构使用,在调用时会从它所表示的控制点处恢复执行。

注意Continuation所保存的控制状态中并不包括数据。关于这一点有个很有趣的“Continuation三明治”的描述:

假设你在厨房里的冰箱前面,正打算做一个三明治。这时你获取一个Continuation放进兜里。

然后从冰箱拿了些火鸡和面包做了个三明治,放在了桌子上。

现在调用兜里的那个Continuation,你会发现你又站在了冰箱前,正打算做个三明治。【传送门,状态机?】

但这时桌子上已经有个三明治了,而且火鸡和面包也不见了。

于是你吃掉了三明治。

Continuation以及与相关的call/cc(Call-with-current-continuation)、CPS(Continuation-passing style)这几个概念比较难理解也容易混淆,要彻底把它们搞明白还真得花一番功夫。Continuation的资料也不多,这里列出几篇中文资料供参考: