tornado协程(coroutine)原理

tornado中的协程是如何工作的

本文将按以下结构进行组织,说明tornado中协程的执行原理

协程定义

Coroutines are computer program components that generalize subroutines for nonpreemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations.。 —— [ 维基百科 ]

我们在平常编程中,更习惯使用的是子例程(subroutine),通俗的叫法是函数,或者过程。子例程,往往只有一个入口(函数调用,实参通过传给形参开始执行),一个出口(函数return,执行完毕,或者引发异常,将控制权转移给调用者)。但协程是子例程基础上,一种更加宽泛定义的计算机程序模块(子例程可以看做协程的特例),它可以有多个入口点,允许从一个入口点,执行到下一个入口点之前暂停,保存执行状态,等到合适的时机恢复执行状态,从下一个入口点重新开始执行,这也是协程应该具有的能力。

定义协程代码块 一个入口点和下一个入口点(或者退出点)中的代码。协程模块 由n个入口点代码,和n个协程代码块组成。第一个入口点通常是一个函 数入口点。其组织形式如:函数入口点->协程代码块->入口点->协程代码块…,入口点和代码块相间。线性模块 一个同步函数的函数体是线性执行的。也就是说一个模块中的每一行代码,相继执行,一个模块在执行中,如果还没有执行完毕,不会去执行其他模块的代码。称这样的代码模块为线性模块。

一个协程模块,如果只含有单一入口点和单一协程代码块(假设这个协程代码块全是同步代码),当然这个协程模块是一个线性执行模块,但是如果含有多个入口点和多个协程代码块,那么就不是一个线性模块。那么执行一个协程模块过程实际是分散的(不同的时间段,执行不同的协程代码块,协程代码块的执行时间段,彼此不相交),但也是顺序的(后一个协程代码块在前一个协程代码块执行结束后才执行)。两个属于同一协程模块的相继协程代码块执行的中间时间间隙,可能有很多其他协程模块的协程代码片段在执行。

生成器和yield语义

谈到协程,必须要说说python语义中的生成器(generator)。

在pep255中提到了”simple generator”和”yield语句”(此时还不是”yield表达式”)的实现。一个basic idea,提供一种函数,能够返回中间结果给调用者,然后维护函数的局部状态,,以便函数当离开后,也能恢复执行。

prep255中举了一个简单的例子,生成斐波那契数列:

:a, b = :yield ba, b = b, a+b

a,b初始化为0,1。当yield b被执行,1被返回给调用者。当fib恢复执行,a,变成了1,b也是1,然后将1返回给调用者,如此循环。generator是一种非常自然的编程方式,因为对于fib来说,它的功能不变,都还是不断生成下一个斐波那契数。而对于fib的调用者来说,fib像一个列表的迭代器,不断迭代,可以获取下一个斐波那契数。

:for num in fib():print num

生成器是一个含有yield表达式的函数,此时该函数叫生成器。一个生成器永远是异步的,即使生成器模块中含有阻塞代码。因为调用一个生成器,生成器的参数会绑定到生成器,结果返回是一个生成器对象,它的类型是types.GeneratorType,不会去执行生成器主模块中的代码。

每次调用一个GeneratorType对象的next方法,生成器函数执行到下一个yield语句或者,或者碰到一个return语句,或者执行到生成器函数结束。

在pep342中,对Generator进一步加强,增加了GeneratorType的send方法,和yield表达式语义。yield表达式,可以作为等号右边的表达式。如果对Generator调用send(None)方法,生成器函数会从开始一直执行到yield表达式。那么下一次对Generator调用send(argument),Generator恢复执行。那么可以在生成器函数体内获得这个argument,这个argument将会作为yield表达式的返回值。

从上面可以看到,Generator已经具备协程的一些能力。如:能够暂停执行,保存状态;能够恢复执行;能够异步执行。

但是此时Generator还不是一个协程。一个真正的协程能够控制代码什么时候继续执行。而一个Generator执行遇到一个yield表达式 或者语句,会将执行控制权转移给调用者。

However, it is still possible to implement coroutines on top of a generator facility, with the aid of a top-level dispatcher routine (a trampoline, essentially) that passes control explicitly to child generators identified by tokens passed back from the generators。 —— [ 维基百科 ]

在维基百科中提到,可以实现一个顶级的调度子例程,将执行控制权转移回Generator,从而让它继续执行。在tornado中,ioLoop就是这样的顶级调度子例程,每个协程模块通过,函数装饰器coroutine和ioLoop进行通信,从而ioLoop可以在协程模块执行暂停后,在合适的时机重新调度协程模块执行。

不过,接下来还不能介绍coroutine和ioLoop,在介绍这两者之前,先得明白tornado中在协程环境中一个非常重要的类Future.

Future类

Future类位于tornado源码的concurrent模块中。Future类的完整代码,请查看tornado的源码。在这里截取一部分代码作为分析之用

::return self._done:self._clear_tb_log():return self._result:raise_exc_info(self._exc_info)self._check_done()return self._result:if self._done:fn(self)else:self._callbacks.append(fn):self._result = resultself._set_done():self._done = Truefor cb in self._callbacks:try:cb(self)except Exception:app_log.exception(‘exception calling callback %r for %r’,cb, self)self._callbacks = NoneFuture类重要成员函数:def done(self): Future的_result成员是否被设置def result(self, timeout=None): 获取Future对象的结果def add_done_callback(self, fn): 添加一个回调函数fn给Future对象。如果这个Future对象已经done,则直接执行fn,否则将fn加入到Future类的一个成员列表中保存。def _set_done(self): 一个内部函数,主要是遍历列表,逐个调用列表中的callback函数,也就是前面add_done_calback加如来的。def set_result(self, result): 给Future对象设置result,并且调用_set_done。也就是说,当Future对象获得result后,所有add_done_callback加入的回调函数就会执行。一个有信念者所开发出的力量,大于99个只有兴趣者。

tornado协程(coroutine)原理

相关文章:

你感兴趣的文章:

标签云: