学习tornado:异步

why asynchronous

tornado是一个异步web framework,说是异步,是由于tornado server与client的网络交互是异步的,底层基于io event loop。可是若是client请求server处理的handler里面有一个阻塞的耗时操做,那么总体的server性能就会降低。html

def MainHandler(tornado.web.RequestHandler):
    def get(self):
        client = tornado.httpclient.HttpClient()
        response = client.fetch("http://www.google.com/")
        self.write('Hello World')

在上面的例子中,tornado server的总体性能依赖于访问google的时间,若是访问google的时间比较长,就会致使总体server的阻塞。因此,为了提高总体的server性能,咱们须要一套机制,使得handler处理都可以经过异步的方式实现。node

幸运的是,tornado提供了一套异步机制,方便咱们实现本身的异步操做。当handler处理须要进行其他的网络操做的时候,tornado提供了一个async http client用来支持异步。python

def MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        client = tornado.httpclient.AsyncHTTPClient()
        def callback(response):
            self.write("Hello World")
            self.finish()

        client.fetch("http://www.google.com/", callback)

上面的例子,主要有几个变化:c++

  • 使用asynchronous decorator,它主要设置_auto_finish为false,这样handler的get函数返回的时候tornado就不会关闭与client的链接。
  • 使用AsyncHttpClient,fetch的时候提供callback函数,这样当fetch http请求完成的时候才会去调用callback,而不会阻塞。
  • callback调用完成以后经过finish结束与client的链接。

asynchronous flaw

异步操做是一个很强大的操做,可是它也有一些缺陷。最主要的问题就是在于callback致使了代码逻辑的拆分。对于程序员来讲,同步顺序的想法是一个很天然的习惯,可是异步打破了这种顺序性,致使代码编写的困难。这点,对于写nodejs的童鞋来讲,可能深有体会,若是全部的操做都是异步,那么最终咱们的代码可能写成这样:git

def MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        client = tornado.httpclient.AsyncHTTPClient()
        def callback1(response):

            def callback2(response):
                self.write("Hello World")
                self.finish()
            client.fetch("http://www.google.com", callback2)

        client.fetch("http://www.google.com/", callback1)

也就是说,咱们可能会写出callback嵌套callback的状况,这个极大的会影响代码的阅读与流程的实现。程序员

synchronous

我我的认为,异步拆散了代码流程这个问题不大,毕竟若是一个逻辑须要过多的嵌套callback来实现的话,那么咱们就须要考虑这个逻辑是否合理了,因此异步通常也不会有过多的嵌套层次。github

虽然我认为异步的callback问题不大,可是若是仍然可以有一套机制,使得异步可以顺序化,那么对于代码逻辑的编写来讲,会方便不少。tornado有一些机制来实现。web

yield

在python里面若是一个函数内部实现了yield,那么这个函数就不是函数了,而是一个生成器,它的整个运行机制也跟普通函数不同,举一个例子:数据库

def test_yield():
    print 'yield 1'
    a = yield 'yielded'
    print 'over', a

t = test_yield()
print 'main', type(t)
ret = t.send(None)
print ret
try:
    t.send('hello yield')
except StopIteration:
    print 'yield over'

输出结果以下:网络

main <type 'generator'>
yield 1
yielded
over hello yield
yield over

从上面能够看到,test_yield是一个生成器,当它第一次调用的时候,只是生成了一个Generator,不会执行。当第一次调用send的时候,生成器被resume,开始执行,而后碰到yield,就挂起,等待下一次被send唤醒。当生成器执行完毕,会抛出StopIteration异常,供外部send的地方知晓。

由于yield很方便的提供了一套函数挂起,运行的机制,因此咱们可以经过yield来将本来是异步的流程变成同步的。

gen

tornado有一个gen模块,提供了Task和Callback/Wait机制用来支持同步模型,以task为例:

def MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.engine
    def get(self):
        client = tornado.httpclient.AsyncHTTPClient()
        response = yield tornado.gen.Task(client.fetch, "http://www.google.com/")
        self.write("Hello World")
        self.finish()

能够看到,tornado的gen模块就是经过yield来进行同步化的。主要有以下须要注意的地方:

  • 使用gen.engine的decorator,该函数主要就是用来管理generator的流程控制。
  • 使用了gen.Task,在gen.Task内部,会生成一个callback函数,传给async fetch,并执行fetch,由于fetch是一个异步操做,因此会很快返回。
  • 在gen.Task返回以后使用yield,挂起
  • 当fetch的callback执行以后,唤醒挂起的流程继续执行。

能够看到,使用gen和yield以后,原先的异步逻辑变成了同步流程,在代码的阅读性上面就有不错的提高,不过对于不熟悉yield的童鞋来讲,开始反而会很迷惑,不过只要理解了yield,那就很容易了。

greenlet

虽然yield很强大,可是它只能挂起当前函数,而没法挂起整个堆栈,这个怎么说呢,譬如我想实现下面的功能:

def a():
    yield 1

def b():
    a()

t = b()
t.send(None)

这个经过yield是没法实现的,也就是说,a里面使用yield,它是一个生成器,可是a的挂起没法将b也同时挂起。也就是说,咱们须要一套机制,使得堆栈在任何地方都可以被挂起和恢复,能方便的进行栈切换,而这套机制就是coroutine。

最开始使用coroutine是在lua里面,它原生提供了coroutine的支持。而后在使用luajit的时候,发现内部是基于fiber(win)和context(unix),也就是说,不光lua,其实c/c++咱们也能实现coroutine。如今研究了go,也是内置coroutine,而且这里极力推荐一篇slide

python没有原生提供coroutine,不知道之后会不会有。但有一个greenlet,能帮咱们实现coroutine机制。并且还有人专门写好了tornado与greenlet结合的模块,叫作greenlet_tornado,使用也很简单

class MainHandler(tornado.web.RequestHandler):
    @greenlet_asynchronous
    def get(self):
        response = greenlet_fetch('http://www.google.com')
        self.write("Hello World")
        self.finish()

能够看到,使用greenlet,能更方便的实现代码逻辑,这点比使用gen更方便,由于这些连写代码的童鞋都不用去纠结yield问题了。

总结

这里只是简单的介绍了tornado的一些异步处理流程,以及将异步同步化的一些方法。另外,这里举得例子都是网络http请求方面的,可是server处理请求的时候,可能还须要进行数据库,本地文件的操做,而这些也是同步阻塞耗时操做,一样能够经过异步来解决的,这里就不详细说明了。

相关文章
相关标签/搜索