协程


一直对协程(coroutine)的概念不很很懂,看了Wiki中关于Coroutine的条目心里有点而谱了,至少知道协程是什么了。

通常我们的子例程,比如我们编程语言中的常用的函数,只有一个入口点(Entry Point),那就是你调用这个函数时执行第一行代码,出口点有可能有多个,比如正常执行完最后一行代码,再比如使用return语言在任意一点退出函数。而数据的交换通过参数和返回值来实现。并且这个线程的调用栈,会有Caller的Stack Frame和Callee的Stack Frame。返回的时候Callee的Stack Frame被销毁。Callee能给Caller的也就是返回值。

什么是协程

协程比这个复杂一点,他可以有多个入口点。另一点是多次进入这个函数的时候,可以保持这个Callee的进程Stack Frame,所以执行中的环境并不会销毁和重建,而是使用上次执行时候的Stack Frame。有点需要说明的就是我们这里只考虑单线程的情况,这就变得好玩的。在单个线程中,你可以采用协作式的调用和环境保持,而不像多线程中的抢占。Wiki中给的例子是这样的:

var q := new queue

coroutine produce 
    loop 
        while q is not full 
            create some new items 
            add the items to q 
        yield to consume

coroutine consume 
    loop 
        while q is not empty 
            remove some items from q 
            use the items 
        yield to produce

你可以指定写作的例程,然后通过Queue来做数据的交换。当前进程让出(yield)控制权给指定例程的时候,指定例程得到执行,而且还保存着上次执行时候的环境,比如内部定义的变量。

这也是像Golang中Goroutine的做法,只不过Goroutine是可以跨多个虚拟进程的。

如果一个协程没有使用yield的让出进程,而是执行完最后一条代码退出,这就跟我们普通的例程没有区别的。换种说法例程是协程的一个“特例”。

生成器

Python语言中生成器(generator),生成器也可以让出控制权,而且保持上次执行时候的环境。这也是Python中generator最常用的地方,做迭代或者计数这样的工作。

比如Python中定义和使用一个计数器。

def gen():
    print "start gen execution"
    for x in xrange(1,10):
        yield x


#调用gen函数创建生成器实例,但是其中的代码并没有执行
#gnext保存了当前生成器的状态,比如还未执行
gnext = gen()

print 'generator is not executed'
#next做了两件事
#1. 如果生成器还没有执行,开始执行。如果已经执行,resume到上次执行的位置继续下次执行
#2. 将当前yield expression的值返回给caller。
print gnext.next()  
print 'first execution and returned the value'

print gnext.next()
print gnext.next()
print gnext.next()

生成器也可以让出控制权,但是跟协程的区别是:生成器是一个能力削弱了的协程,他只能将控制权交给Caller,而不能将控制权交给指定例程。

那如果要实现producer->comsumer这样的模式的话,需要一个控制例程也就是caller来协调producer和comsumer这两个生成器实例。

实现协程

协程最大的优势就是能保持当前的环境,所以在实现的时候如果语言本身不支持协程,可以使用闭包(Closure),用Closure去保持执行的状态,然后使用flag去控制执行流程。

实现了协程的语言在实现上也有很大的不同,但是基本原则就是解决两个问题:

1,保持当前协程的Stack在推出后不被释放

2,跳到上次yield位置继续执行剩下的代码

published at Jan. 1, 2015, 8 a.m.

Comments:

Sharing your thoughts: