详细解读tornado协程(coroutine)原理
|
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中举了一个简单的例子,生成斐波那契数列:
def fib():
a,b = 0,1
while 1:
yield b
a,b = b,a+b
a,b初始化为0,1。当yield b被执行,1被返回给调用者。当fib恢复执行,a,变成了1,b也是1,然后将1返回给调用者,如此循环。generator是一种非常自然的编程方式,因为对于fib来说,它的功能不变,都还是不断生成下一个斐波那契数。而对于fib的调用者来说,fib像一个列表的迭代器,不断迭代,可以获取下一个斐波那契数。
def caller():
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的源码。在这里截取一部分代码作为分析之用
class Future(object):
def done(self):
return self._done
def result(self,timeout=None):
self._clear_tb_log()
if self._result is not None:
return self._result
if self._exc_info is not None:
raise_exc_info(self._exc_info)
self._check_done()
return self._result
def add_done_callback(self,fn):
if self._done:
fn(self)
else:
self._callbacks.append(fn)
def set_result(self,result):
self._result = result
self._set_done()
def _set_done(self):
self._done = True
for cb in self._callbacks:
try:
cb(self)
except Exception:
app_log.exception('exception calling callback %r for %r',cb,self)
self._callbacks = None
Future类重要成员函数:
Future的_result成员是否被设置
获取Future对象的结果
添加一个回调函数fn给Future对象。如果这个Future对象已经done,则直接执行fn,否则将fn加入到Future类的一个成员列表中保存。
一个内部函数,主要是遍历列表,逐个调用列表中的callback函数,也就是前面add_done_calback加如来的。 def set_result(self,result): 给Future对象设置result,并且调用_set_done。也就是说,当Future对象获得result后,所有add_done_callback加入的回调函数就会执行。 Future封装了异步操作的结果。实际是它类似于在网页html前端中,图片异步加载的占位符,但加载后最终也是一个完整的图片。Future也是同样用处,tornado使用它,最终希望它被set_result,并且调用一些回调函数。Future对象实际是coroutine函数装饰器和IOLoop的沟通使者,有着非常重要的作用。 IOLoop类tornado框架的底层核心类,位于tornado的ioloop模块。功能方面类似win32窗口的消息循环。每个窗口可以绑定一个窗口过程。窗口过程主要是一个消息循环在执行。消息循环主要任务是利用PeekMessage系统调用,从消息队列中取出各种类型的消息,判断消息的类型,然后交给特定的消息handler进行执行。 tornado中的IOLoop与此相比具有很大的相似性,在协程运行环境中担任着协程调度器的角色,和win32的消息循环本质上都是一种事件循环,等待事件,然后运行对应的事件处理器(handler)。不过IOLoop主要调度处理的是IO事件(如读,写,错误)。除此之外,还能调度callback和timeout事件。 在本博文中,我们暂时只关注callback事件,因为这个与协程调度的相关性最大。
def add_future(self,future,callback):
assert is_future(future)
callback = stack_context.wrap(callback)
future.add_done_callback(
lambda future: self.add_callback(callback,future))
add_future函数在基类IOLoop中实现,函数参数是一个Future对象和一个callback函数。当Future对象被set_result,执行一个回调函数,是个lambda函数,在lambda函数中调用IOLoop的add_callback函数。将add_future的参数callback加入到IOLoop的统一调度中,让callback在IOLoop下一次迭代中执行。
def add_callback(self,callback,*args,**kwargs):
with self._callback_lock:
if self._closing:
raise RuntimeError("IOLoop is closing")
list_empty = not self._callbacks
self._callbacks.append(functools.partial(
stack_context.wrap(callback),**kwargs))
if list_empty and thread.get_ident() != self._thread_ident:
self._waker.wake()
add_callback函数主要在IOLoop的子类PollIOLoop中实现。也很容易理解。 将传入的callback函数,利用偏函数进行包装,将所有callback真正运行时需要的参数,都绑定到生成的偏函数中,实际上就是找个地方把callback运行时需要的参数保存起来。将包装好的偏函数加入到回调函数列表。当IOLoop下一次迭代运行的时候,遍历callback函数列表,运行偏函数的时候,就不再需要传入参数执行,效果等同于用实参运行callback。 IOLoop对象调用start函数,会运行event loop。在event loop中,首先遍历callback列表,执行回调函数,然后遍历timeout列表,执行timeoutCallback。最后才执行ioHandler。 coroutine函数装饰器(编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
