简单介绍Python的Tornado框架中的协程异步实现原理
Tornado4.0已经发布了很长一段时间了,新版本广泛的应用了协程(Future)特性.我们目前已经将Tornado升级到最新版本,而且也大量的使用协程特性.
很长时间没有更新博客,今天就简单介绍下Tornado协程实现原理,Tornado的协程是基于Python的生成器实现的,所以首先来回顾下生成器.
生成器
Python的生成器可以保存执行状态并在下次调用的时候恢复,通过在函数体内使用yield关键字来创建一个生成器,通过内置函数next或生成器的next方法来恢复生成器的状态.
deftest(): yield1
我们调用test函数,此时并不会返回结果,而是会返回一个生成器
>>>test() <generatorobjecttestat0x100b3b320>
我们调用其next方法则返回yield关键字之后的内容.
>>>t=test() >>>t.next() 1
如果我们接着调用next方法,后面又没有yield关键字继续返回的话,会抛出一个StopIteration异常.
yield关键字不仅仅能从生成器内部返回状态,同时也可以将外部信息传递到生成器内部,通过将yeild关键里赋值给变量,并调用生成器的send方法来将对象传递到生成器内部.需要注意的是生成器的开始必须调用其next方法,后面send方法调用的同时也会触发next动作.如果没有变量接收yield关键字那么send传递的值将会被丢弃.
>>>deftest(): a=yield print(a)
首先调用next上面函数返回的生成器将返回None,如果这时候直接调用next将会给生成器发送None,如果调用send发送一个值,将打印这个值并抛出StopIteration异常.
一个简单地协程
以上就是实现协程的所有基础,为了加深理解,我们这里写一个小例子,例子我们只使用协程开启两个甚至多个死循环,下面就是一个极其简单地例子::
#!/usr/bin/envpython #-*-coding:utf-8-*-
from__future__importabsolute_import,print_function,division,with_statement defloop1(): """循环1负责抛出一个函数和对应的参数,并接收结果 """ a=0 ret=1 whileTrue: ret=yieldsum,[a,ret] a,ret=ret,a print("Loop1ret",ret)
defloop2(): """循环2负责接收函数并计算结果,然后yield出结果 """ whileTrue: func,args=yield yieldfunc(args) print("Loop2") l1=loop1() l2=loop2() tmp=l1.next() foriinrange(10): l2.next() ret=l2.send(tmp) tmp=l1.send(ret)
上面例子里loop1负责产生任务,loop2负责执行任务,主循环负责调度任务并将任务结果发回给任务产生者.
Tornado如何做的
我们首先看一个使用Tornado协程异步的例子
#!/usr/bin/envpython #-*-coding:utf-8-*- from__future__importabsolute_import,print_function,division,with_statement fromtornadoimportgen fromtornadoimportweb fromtornadoimporthttpclient classActionHandler(web.RequestHandler): @gen.coroutine defget(self): response=yieldhttpclient.AsyncHTTPClient().fetch("http://www.linuxzen.com") #...
其实原理在上面简单地例子里已经讲清楚了,我们来简单分析一遍上面的例子,首先Tornado得到ActionHandler.get方法抛出(next)的一个任务,然后异步的去执行任务,当任务(网络请求)结束或异常时Tornado取得事件通知然后将结果放回(send)到该方法中让该方法继续执行.
由于是异步的,调用这个方法并不会阻塞其他任务执行.
这时候我们的方法其实就是上个例子loop1函数,而Tornado调度并执行了其抛出的任务.
总结
Tornado的协程异步可以让异步看起来是顺序执行的,可以从一大串的callback中解脱出来.
Tornado的协程异步并不是这三言两语能说清楚的,其中有很复杂的封装和传递,有兴趣可以自己阅读源码.