tornado使用者指引(二)------------tornado協程實現原理和使用(一)
摘要:Tornado建議使用協程來實現非同步呼叫。協程使用python的yield關鍵字來繼續或者暫停執行,而不用編寫大量的callback函式來實現。(在linux基於epoll的非同步呼叫中,我們需要自己顯式的為非同步執行結果安裝大量的callback函式).協程的使用和編寫非同步程式碼一樣簡單,而且省去了執行緒的開銷。協程使編寫併發程式更加容易,而且沒有上下文切換的開銷。舉例:
from tornado import gen
@gen.coroutine
def fetch_coroutine(url)
Tornado建議使用協程來實現非同步呼叫。
協程使用python的yield關鍵字來繼續或者暫停執行,而不用編寫大量的callback函式來實現。(在linux基於epoll的非同步呼叫中,我們需要自己顯式的為非同步執行結果安裝大量的callback函式).
協程的使用和編寫非同步程式碼一樣簡單,而且省去了執行緒的開銷。
協程使編寫併發程式更加容易,而且沒有上下文切換的開銷。
舉例:
from tornado import gen
@gen.coroutine
def fetch_coroutine(url):
http_client = AsyncHTTPClient()
response = yield http_client.fetch(url)
# 在python3.3以前的版本中, 在一個生成器函式中返回值是不允許的。你需要通過丟擲異常的方式來達到相同的目的。
return response.body
@gen.coroutine是tornado實現的一個生成器,用來將fetch_coroutine函式包裝為一個協程,可以把這個協程當作一個和執行緒等價的執行體。這個執行體會被tornado總的ioloop呼叫,並在需要阻塞時切換到其它協程執行,當阻塞呼叫完成時,ioloop會繼續執行這個就緒的協程。通過這樣的方式,可以在一個執行緒中執行多個協程,而且不會因為某個協程阻塞而阻塞其它就緒的協程。
實現原理:
包裝後的函式會返回一個Future,當這個協程真正執行完成後,會設定Future的執行結果。可以通過yield來等待這個協程執行完成並得到結果。
當協程執行yield http_client.fetch時,由於這個操作是一個網路I/O操作,屬於阻塞操作。因此這個協程會暫停執行,進而tornado總的ioloop可以執行其它協程。
在協程裡面,yield語句阻塞的函式需要返回一個Future,這個Future代表了一個非同步執行體,並在非同步操作執行完成時設定Future的結果,tornado會在Future執行完成時,將Future的執行結果通過生成器的send方法將值傳回yield語句,進而喚醒阻塞的協程繼續執行。
在實現上,http_client.fetch操作會通過將fd加入tornado ioloop的方式實現真正的非同步操作,當fetch真正成功時,即epoll返回時,會設定Future的結果,並將此協程喚醒繼續執行。
綜上所述,我們要利用tornado的協程功能,需要用@gen.coroutine包裝我們的函式,並在需要阻塞的地方用yield語句阻塞。阻塞的程式碼需要返回一個Future,並通過某種非同步方式將Future的執行結果設定好。
使用tornado協程舉例:
1.我們可以在協程不執行任何阻塞操作,這樣協程會一直執行直到完成:
我們建立2個協程和一個總的協程,由於協程裡沒有阻塞操作,所以實際上兩個協程是順序執行完成的。
from tornado import gen
from tornado.ioloop import IOLoop
@gen.coroutine
def cor(n,str):
for i in range(n):
print(str,i)
return [email protected]
@gen.coroutine
def main():
cor(3,"first")
cor(4,"second")
IOLoop.instance().run_sync(main)
2.有阻塞操作的協程: 我們在協程迴圈中增加了睡眠操作,這個sleep是tornado框架實現的,注意上面分析的過程,這個yield需要返回一個future. 另外,在main中我們也增加了yield操作,是因為要等待2個協程執行完再結束ioloop.否則程式會直接結束。 這樣,可以看到2個協程交替執行,sleep操作並不會阻塞另外一個協程。
from tornado import gen
from tornado.ioloop import IOLoop
@gen.coroutine
def cor(n,str):
for i in range(n):
print(str,n)
yield gen.sleep(1)
return [email protected]
@gen.coroutine
def main():
cor(3,"first")
cor(3,"second")
yield gen.sleep(3)
IOLoop.instance().run_sync(main)
3.如果把2中main函式程式碼改成下面這樣,這樣main協程就會等待第一個協程執行完,才會執行第2個協程。
@gen.coroutine
def main():
yield cor(3,"first")
yield cor(3,"second")
IOLoop.instance().run_sync(main)