起源
协程的概念源自Melvin Conway对COBOL编译器的设计:
“在 Conway 的设计里,词法和语法解析不再是两个独立运行的步骤,而是交织在一起。编译器的控制流在词法和语法解析之间来回切换:当词法模块读入足够多的 token 时,控制流交给语法分析;当语法分析消化完所有 token 后,控制流交给词法分析。词法和语法分别独立维护自身的运行状态。”
协程概念的关键在于控制流的主动让出和恢复。
定义
在cs中routine被定义为一个操作序列。几个routine的执行形成parent-child关系,而且child永远在parent之前终止。Coroutine是routine概念的泛化。两者间的主要不同在于一个Coroutine能够通过额外的操作显式的挂起和恢复它的进度,这是通过保存执行状态从而提供了一个强化控制流(维护执行上下文)做到的。
如果Coroutine的调用方式和routine一样,每次调用都会使栈增长并且永远不会出栈。跳转到Coroutine中间是不可能的,因为返回地址将在栈顶的入口处。
解决方案是每个Coroutine都有自己的栈和控制块(control block)。在Coroutine被挂起之前,当前Coroutine的非易失性寄存器(包括栈和指令/程序指针)会被保存到Coroutine的控制块中。新被激活的Coroutine必须在它被恢复之前从它的控制块中恢复它的寄存器。
这里的上下文切换不需要系统特权并且为c++提供了便利的多任务协作。Coroutine提供准确的并行。当一个程序要同时做多件事情时,在Coroutine的帮助下能比只有一个控制流情况更简单和优雅。这个优点在使用递归函数时特别明显,例如二叉树的遍历。
Coroutine具有局部变量的值在连续调用(context switches)之间得到保持、当控制流离开Coroutine时执行被挂起并且在之后被恢复等特点。
对称与非对称
一个非对称的Coroutine被称为invoker,使用一个特殊的操作将控制隐式的让给它的invoker。相反的,所有对称Coroutine都是相等的;一个对称Coroutine可以将控制传递给任何一个其他的Coroutine。因此,一个非对称Coroutine必须指定它要让出控制的那个Coroutine。
有栈与无栈
有栈无栈的区别在于挂起时是否需要栈。无栈Coroutine在挂起时不需要栈,不能在需要栈的地方挂起,比如说函数传参。只在Coroutine运行时需要栈。有栈Coroutine在挂起时需要栈,可以在任何地方将Coroutine挂起。
总的来说,有栈Coroutine比无栈Coroutine更有用,但是无栈Coroutine更高效。因为有栈Coroutine一般需要分配一定内存来存放它的运行时栈,它的上下文切换比无栈的更expensive。而无栈Coroutine只需要恢复程序指针。
对于无栈Coroutine,并不是它没有运行时栈,“无栈”只是意味着它们使用同一个运行时栈。
应用
Coroutine为cooperative tasks(fibers)、迭代器、生成器、pipe等组件提供了实现上的支持。
参考资料:
https://blog.youxu.info/2014/12/04/coroutine/
http://www.boost.org/doc/libs/1_66_0/libs/coroutine2/doc/html/coroutine2/intro.html
https://*.com/questions/28977302/how-do-stackless-coroutines-differ-from-stackful-coroutines
https://blog.varunramesh.net/posts/stackless-vs-stackful-coroutines/