介紹Python的Tornado框架中的協(xié)程異步實現(xiàn)原理
這篇文章主要介紹了簡單介紹Python的Tornado框架中的協(xié)程異步實現(xiàn)原理,作者基于Python的生成器講述了Tornado異步的特點,需要的朋友可以參考下
Tornado 4.0 已經(jīng)發(fā)布了很長一段時間了, 新版本廣泛的應(yīng)用了協(xié)程(Future)特性. 我們目前已經(jīng)將 Tornado 升級到最新版本, 而且也大量的使用協(xié)程特性.
很長時間沒有更新博客, 今天就簡單介紹下 Tornado 協(xié)程實現(xiàn)原理, Tornado 的協(xié)程是基于 Python 的生成器實現(xiàn)的, 所以首先來回顧下生成器.
生成器
Python 的生成器可以保存執(zhí)行狀態(tài) 并在下次調(diào)用的時候恢復(fù), 通過在函數(shù)體內(nèi)使用 yield 關(guān)鍵字 來創(chuàng)建一個生成器, 通過內(nèi)置函數(shù) next 或生成器的 next 方法來恢復(fù)生成器的狀態(tài).
def test():
yield 1
我們調(diào)用 test 函數(shù), 此時并不會返回結(jié)果, 而是會返回一個生成器
>>> test()
<generator object test at 0x100b3b320>
我們調(diào)用其 next 方法則返回 yield 關(guān)鍵字之后的內(nèi)容.
>>> t = test()
>>> t.next()
如果我們接著調(diào)用 next 方法, 后面又沒有 yield 關(guān)鍵字繼續(xù)返回的話, 會拋出一個 StopIteration 異常.
yield 關(guān)鍵字不僅僅能從生成器內(nèi)部返回狀態(tài), 同時也可以將外部信息傳遞到生成器內(nèi)部, 通過將 yeild 關(guān)鍵里賦值給變量, 并調(diào)用生成器的 send 方法來將對象傳遞到生成器 內(nèi)部. 需要注意的是生成器的開始必須調(diào)用其 next 方法, 后面 send 方法調(diào)用的同時 也會觸發(fā) next 動作. 如果沒有變量接收 yield 關(guān)鍵字那么 send 傳遞的值將會 被丟棄.
>>> def test():
a = yield
print(a)
首先調(diào)用 next 上面函數(shù)返回的生成器將返回 None, 如果這時候直接調(diào)用 next 將 會給生成器發(fā)送 None, 如果調(diào)用 send 發(fā)送一個值, 將打印這個值并拋出 StopIteration 異常.
一個簡單地協(xié)程
以上就是實現(xiàn)協(xié)程的所有基礎(chǔ), 為了加深理解, 我們這里寫一個小例子, 例子我們只使用協(xié)程 開啟兩個甚至多個死循環(huán), 下面就是一個極其簡單地例子::
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from __future__ import absolute_import, print_function, division, with_statement
def loop1():
""" 循環(huán)1負責拋出一個函數(shù)和對應(yīng)的參數(shù), 并接收結(jié)果
"""
a = 0
ret = 1
while True:
ret = yield sum, [a, ret]
a, ret = ret, a
print("Loop1 ret", ret)
def loop2():
""" 循環(huán)2 負責接收函數(shù)并計算結(jié)果, 然后 yield 出結(jié)果
"""
while True:
func, args = yield
yield func(args)
print("Loop2")
l1 = loop1()
l2 = loop2()
tmp = l1.next()
for i in range(10):
l2.next()
ret = l2.send(tmp)
tmp = l1.send(ret)
上面例子里 loop1 負責產(chǎn)生任務(wù), loop2 負責執(zhí)行任務(wù), 主循環(huán)負責調(diào)度任務(wù)并將任務(wù)結(jié)果發(fā)回給 任務(wù)產(chǎn)生者.
Tornado 如何做的
我們首先看一個使用 Tornado 協(xié)程異步的例子
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from __future__ import absolute_import, print_function, division, with_statement
from tornado import gen
from tornado import web
from tornado import httpclient
class ActionHandler(web.RequestHandler):
@gen.coroutine
def get(self):
response = yield httpclient.AsyncHTTPClient().fetch("")
# ...
其實原理在上面簡單地例子里已經(jīng)講清楚了, 我們來簡單分析一遍上面的例子, 首先 Tornado 得到 ActionHandler.get 方法拋出(next)的一個任務(wù), 然后異步的去執(zhí)行任務(wù), 當任務(wù)(網(wǎng)絡(luò)請求)結(jié)束或 異常時 Tornado 取得事件通知然后將結(jié)果放回(send)到該方法中讓該方法繼續(xù)執(zhí)行.
由于是異步的, 調(diào)用這個方法并不會阻塞其他任務(wù)執(zhí)行.
這時候我們的方法其實就是上個例子 loop1 函數(shù), 而 Tornado 調(diào)度并執(zhí)行了其拋出的任務(wù).
總結(jié)
Tornado 的協(xié)程異步可以讓異步看起來是順序執(zhí)行的, 可以從一大串的 callback 中解脫出來.
Tornado 的協(xié)程異步并不是這三言兩語能說清楚的, 其中有很復(fù)雜的封裝和傳遞, 有興趣可以自己 閱讀源碼.
更多信息請查看IT技術(shù)專欄