Tornado 協程

同步異步I/O客戶端python

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()

協程mysql

一、編寫協程函數git

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)

二、調用協程函數github

因爲Tornado協程基於python的yield關鍵字實現,因此不能調用普通函數同樣調用協程函數web

協程函數可經過如下三種方式調用sql

  • 在自己是協程的函數內經過yield關鍵字調用
  • 在IOLoop還沒有啓動時,經過IOLoop的run_sync()函數調用
  • 在IOLoop已經啓動時,經過IOLoop的spawn_callback()函數調用

  下面是一個經過協程函數調用協程函數的例子mongodb

@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能用該函數調用沒有返回值的協程函數

三、在協程中調用阻塞函數

在協程中直接調用阻塞函數會影響協程自己的性能,因此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()

四、在協程中的等待多個異步調用

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修飾器,將默認的同步機制還成協程機制

一、異步化

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才知道本次處理已經完成,能夠發送響應給客戶端

異步雖然提升了併發能力,可是編程方法更繁瑣

二、協程化

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

相關文章
相關標籤/搜索