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++
異步操做是一個很強大的操做,可是它也有一些缺陷。最主要的問題就是在於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的狀況,這個極大的會影響代碼的閱讀與流程的實現。程序員
我我的認爲,異步拆散了代碼流程這個問題不大,畢竟若是一個邏輯須要過多的嵌套callback來實現的話,那麼咱們就須要考慮這個邏輯是否合理了,因此異步通常也不會有過多的嵌套層次。github
雖然我認爲異步的callback問題不大,可是若是仍然可以有一套機制,使得異步可以順序化,那麼對於代碼邏輯的編寫來講,會方便不少。tornado有一些機制來實現。web
在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來將本來是異步的流程變成同步的。
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和yield以後,原先的異步邏輯變成了同步流程,在代碼的閱讀性上面就有不錯的提高,不過對於不熟悉yield的童鞋來講,開始反而會很迷惑,不過只要理解了yield,那就很容易了。
雖然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處理請求的時候,可能還須要進行數據庫,本地文件的操做,而這些也是同步阻塞耗時操做,一樣能夠經過異步來解決的,這裏就不詳細說明了。