同步異步I/O客戶端
from tornado.httpclient import HTTPClient,AsyncHTTPClient def ssync_visit(): http_client = HTTPClient() response = http_client.fetch('www.baidu.com') # 阻塞,直到網站請求完成 print(response.body) def hendle_response(response): print(response.body) def async_visit(): http_client = AsyncHTTPClient() http_client.fetch('www.baidu.com',callback=hendle_response) # 非阻塞 async_visit()
協程
1、編寫協程函數
from tornado import gen # 引入協程庫 from tornado.httpclient import AsyncHTTPClient @gen.coroutine def coroutine_visit(): http_client = AsyncHTTPClient() response = yield http_client.fetch('www.baidu.com') print(response.body)
2、調用協程函數
由於Tornado協程基於python的yield關鍵字實現,所以不能調用普通函數一樣調用協程函數
協程函數可通過以下三種方式調用
- 在本身是協程的函數內通過yield關鍵字調用
- 在IOLoop尚未啟動時,通過IOLoop的run_sync()函數調用
- 在IOLoop已經啟動時,通過IOLoop的spawn_callback()函數調用
下面是一個通過協程函數調用協程函數的例子
@gen.coroutine def outer_coroutine(): print('開始調用另一個協程') yield coroutine_visit() print('outer_coroutine 調用結束')
outer_coroutine和coroutine_visit都是協程函數,他們之間可以通過yield關鍵字進行調用
IOLoop 是Tornado的主事件循環對象,Tornado程序通過它監聽外部客戶端的訪問請求,並執行相應的操作,當程序尚未進入IOLoop的runing狀態時,可以通過run_sync()函數調用協程函數,比如:
from tornado import gen # 引入協程庫 from tornado.ioloop import IOLoop from tornado.httpclient import AsyncHTTPClient @gen.coroutine def coroutine_visit(): http_client = AsyncHTTPClient() response = yield http_client.fetch('http://www.baidu.com') print(response.body) def func_normal(): print('開始調用協程') IOLoop.current().run_sync(lambda: coroutine_visit()) print('結束協程調用') func_normal()
本例中run_sync()函數將當前函數的執行進行阻塞,直到被調用的協程執行完成
Tornado 要求協程函數在IOloop的running狀態才能被調用,只不過run_sync函數自動完成了啟動,停止IOLoop的步驟,他的實現邏輯為:啟動IOLoop-調用被lambda封裝的協程函數-停止IOLoop
當tornado程序已經處於running 狀態時協程的調用如下:
def func_normal(): print('開始調用協程') IOLoop.current().spawn_callback(coroutine_visit) print('結束協程調用') func_normal()
開始調用協程
結束協程調用
本例中spawn_callback函數不會等待被調用的協程執行完成,而協程函數將會由IOLoop在合適的時機進行調用,並且spawn_callback函數沒有提供電泳返回值的方法,所以hi能用該函數調用沒有返回值的協程函數
3、在協程中調用阻塞函數
在協程中直接調用阻塞函數會影響協程本身的性能,所以tornado提供了在協程中利用線程池調度阻塞函數,從而不影響協程本身繼續執行的方法,實例代碼如下:
from concurrent.futures import ThreadPoolExecutor from tornado import gen thread_pool = ThreadPoolExecutor(2) def mySleep(count): import time for i in range(count): time.sleep(1) @gen.coroutine def call_backing(): print('開始調用當前函數') yield thread_pool.submit(mySleep,10) print('結束調用') call_backing()
4、在協程中的等待多個異步調用
tornado允許在協程中用一個yield關鍵字等待多個異步調用,只需把這些調用用列表或字典的方式傳遞給yield關鍵字即可
實例如下:
from tornado import gen # 引入協程庫 from tornado.ioloop import IOLoop from tornado.httpclient import AsyncHTTPClient @gen.coroutine def coroutine_visit(): http_client = AsyncHTTPClient() list_response = yield [http_client.fetch('http://www.baidu.com'), http_client.fetch('http://www.sina.com'), http_client.fetch('http://www.163.com') ] for response in list_response: print(response.body) def func_normal(): print('開始調用協程') IOLoop.current().run_sync(lambda: coroutine_visit()) print('結束協程調用') func_normal()
字典同理,不再演示
Tornado 網站
異步化,協程化
當大量客戶端高並發請求場景出現時,需要用到兩種方式改變同步的處理請求流程
- 異步化:針對RequestHandler的處理函數使用@tornado.web.asynchronous修飾器,將默認同步機制改成異步機制
- 協程化:針對RequestHandler的處理函數使用@tornado.gen.coroutine修飾器,將默認的同步機制還成協程機制
1、異步化
from tornado import web,httpclient import tornado class MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): http = tornado.httpclient.AsyncHTTPClient() http.fetch('http://www.baidu.com',callback=self.on_response) def on_response(self,response): if response.error: raise tornado.web.HTTPError(500) self.write(response.body) self.finish()
用@tornado.web.asynchronous 定義HTTP訪問處理函數get(),當get函數返回時對該訪問的請求尚未完成,所以tornado無法發送響應給客戶端,只有隨后的回掉函數中的finsh函數被調用時,tornado才知道本次處理已經完成,可以發送響應給客戶端
異步雖然提高了並發能力,但是編程方法更繁瑣
2、協程化
tornado 協程結合同步異步的優點,
import tornado.web import tornado.httpclient class MainHandler(tornado.web.RequestHandler): @tornado.gen.coroutine def get(self): http = tornado.httpclient.AsyncHTTPClient() response = yield http.fetch('http://www.baidu.com') self.write(response.body)
用tornado.gen.coroutine裝飾MainHandler的get(),post()函數
使用異步對象處理耗時操作,比如AsyncHTTPClient
調用yield關鍵字獲取異步對象的處理結果
實踐中的異步
下各項同步(阻塞)的,如果在 tornado 中按照之前的方式只用它們,就是把 tornado 的非阻塞、異步優勢削減了。
- 數據庫的所有操作,不管你的數據是 SQL 還是 noSQL,connect、insert、update 等
- 文件操作,打開,讀取,寫入等
- time.sleep
- smtplib,發郵件的操作
- 一些網絡操作,比如 tornado 的 httpclient 以及 pycurl 等
解決方法
- 在數據庫方面,由於種類繁多,不能一一說明,比如 mysql,可以使用adb模塊來實現 python 的異步 mysql 庫;對於 mongodb 數據庫,有一個非常優秀的模塊,專門用於在 tornado 和 mongodb 上實現異步操作,它就是 motor。
- 文件操作方面也沒有替代模塊,只能盡量控制好 IO,或者使用內存型(Redis)及文檔型(MongoDB)數據庫。
- time.sleep() 在 tornado 中有替代:yield
tornado.gen.sleep()
或者tornado.ioloop.IOLoop.instance().add_timeout
- smtp 發送郵件,推薦改為 tornado-smtp-client。
- 對於網絡操作,要使用 tornado.httpclient.AsyncHTTPClient。
greenlet-tornado 可以實現用專門的庫來實現tornado 的異步而不使用裝飾器的異步