學習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處理請求的時候,可能還須要進行數據庫,本地文件的操做,而這些也是同步阻塞耗時操做,一樣能夠經過異步來解決的,這裏就不詳細說明了。

相關文章
相關標籤/搜索