Tornado使用-簡介

weixin_34015860發表於2017-12-15

1.什麼是Tornado
Tornado是一個python web框架,和一個非同步網路通訊庫。
因為它的非阻塞網路IO,可同時支撐萬級別的連線請求。
適用於長輪詢,全雙工websocket通訊。

2.主要模組
1)web框架:RequestHandler
2)web伺服器:客戶端,服務端(HTTPServer,AsyncHTTPClient)
3)非同步網路庫:IOLoop,IOStream
4)協程庫:tornado.gen可以使非同步程式碼用一種更加直接的方式去寫,而不是回撥鏈的方式

3.注意事項
Tornado的web框架,和伺服器框架是基於WSGI的,但是同時使用效率會更高。

4.非同步和非阻塞IO
1)為什麼要用非同步和非阻塞IO
傳統的同步請求的web伺服器,當有多個使用者同時請求時,會為每個使用者建立一個獨立的執行緒,這是非常昂貴的。
Tornado使用單執行緒的事件迴圈來處理請求,也就是說任何時間只會有一個操作是啟用狀態的,這就要求應用程式的所有操作都應該是非同步非阻塞的。

2)阻塞
一個函式在執行的過程中等待某個操作完成通知,才能繼續執行後續的程式碼。
這個操作可能是網路IO,磁碟IO,或者一些耗時的計算等,此時函式執行會阻塞,長時間佔用CPU時間。

3)非同步
非同步函式在執行完成之前返回,耗時操作會在後臺執行或下次執行,應用程式可以去執行其他的操作。
耗時操作執行完畢後,會通過某種方式告訴應用程式,應用程式會接著執行未完成的函式,通知方式有以下幾種。
回撥
返回佔位符(Future,Promise,Deferred),即未來會自動執行的物件
放入待處理佇列
訊號

4)同步,非同步示例
同步請求:

from tornado.httpclient import HTTPClient

def sync_fetch():
    http_client = HTTPClient()
    response = http_client.fetch("http://www.baidu.com")
    print(response.body)
sync_fetch()

非同步請求:
使用callback回撥

from tornado.httpclient import AsyncHTTPClient, HTTPClient

def handle_response(response):
    print(response.body)

http_client = AsyncHTTPClient()
http_client.fetch("http://www.baidu.com", handle_response)
http_client.close()

使用佔位符,也就是協程,將來執行物件

from tornado.httpclient import AsyncHTTPClient, HTTPClient
from tornado.concurrent import Future

http_client = AsyncHTTPClient()
my_future = Future()
fetch_future =http_client.fetch("http://www.baidu.com")
fetch_future.add_done_callback(
    lambda f:my_future.set_result(f.result())
)  

使用協程

from tornado.httpclient import AsyncHTTPClient, HTTPClient
from tornado import gen

@gen.coroutine
def fetch_coroutine():
    http_client = AsyncHTTPClient()
    response = yield http_client.fetch('http://www.baidu.com')
    raise gen.Return(response.body)

raise gen.Return(response.body):返回結果

5.協程
coroutines是Tornado推薦的非同步程式碼編寫方式,使用yield關鍵字掛起,恢復執行,來代替回撥鏈。
協程的編寫方式簡單,並且減少了上下文切換,提升了效率。
1)python2中程式碼示例

from tornado.httpclient import AsyncHTTPClient, HTTPClient
from tornado import gen

@gen.coroutine
def fetch_coroutine():
    http_client = AsyncHTTPClient()
    response = yield http_client.fetch('http://www.baidu.com')
    raise gen.Return(response.body)

2)python3中程式碼示例

async def fetch_coroutine(url):
    http_client = AsyncHTTPClient()
    response = await http_client.fetch(url)
    return response.body

3)協程的原理
函式中包含yield關鍵字,那這個函式就是一個生成器,生成器是非同步執行的。
生成器和函式的區別是,執行到yield關鍵字的地方返回,呼叫next()方法後,會繼續接著yield表示式執行。
@gen.coroutine裝飾器的作用是從生成器接收一個Future,等待(非阻塞)Future執行完成,傳送Future結果到生成器,作為yield表示式的結果。

4)怎麼執行協程
呼叫協程函式的函式必須也是個協程

@gen.coroutine
def divide(x, y):
    return x / y

def bad_call():
    # 錯誤
    divide(1, 0)

@gen.coroutine
def good_call():
    # 正確
    yield divide(1, 0)

協程的執行方式有兩種:
IOLoop.spawn_callback
在下一次IOLoop迴圈時,執行協程函式.spawn_callback不需要呼叫者的上下文空間,適合獨立的協程函式的執行。

IOLoop.current().spawn_callback(divide, 1, 0)

IOLoop.run_sync
開始一個IOLoop迴圈,執行給定的協程函式,結束IOLoop迴圈。
函式的返回結果必須是一個可以yieldable的物件或者None

tornado.ioloop.IOLoop.current().run_sync(lambda :divide(1, 2))

5)協程的幾種模式
和callback互動
使用gen.Task包裹函式,gen.Task返回Future,可以yield

@gen.coroutine
def call_task():
    yield gen.Task(some_function, other_args)

協程函式中呼叫同步函式
使用ThreadPoolExecutor類,可以返回相容協程的Futures

thread_pool = ThreadPoolExecutor(4)

@gen.coroutine
def call_blocking():
    yield thread_pool.submit(blocking_func, args)

等待所有Future物件執行完畢

@gen.coroutine
def parallel_fetch(url1, url2):
    resp1, resp2 = yield [http_client.fetch(url1),
                          http_client.fetch(url2)]

迴圈

import motor
db = motor.MotorClient().test

@gen.coroutine
def loop_example(collection):
    cursor = db.collection.find()
    while (yield cursor.fetch_next):
        doc = cursor.next_object()