Tornado 協程


同步異步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 的異步而不使用裝飾器的異步


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM