tornado.gen.coroutine-編寫異步函數

異步函數:python

1. 返回Futureweb

2. 必須有set_result( )或者set_exception( )調用。數據庫


這裏展現一個異步socket讀取的例子:服務器


首先定義一個定時返回的服務器,來模擬耗時的操做app

from tornado.tcpserver import TCPServer
from tornado import ioloop
from tornado import gen 
from tornado.concurrent import Future

def sleep(duration):
    f = Future()
    ioloop.IOLoop.current().call_later(duration, lambda: f.set_result(None))
    return f

def handle_excep(future):
    if future.exception() is not None:
        print future.exc_info()
    

class EchoServer(TCPServer):
    def handle_stream(self, stream, address):
        f = self._handle_stream(stream, address)
        f.add_done_callback(handle_excep)

    @gen.coroutine
    def _handle_stream(self, stream, address):
        yield data = yield stream.read_until('\n')        
        yield sleep(2)
        yield stream.write(data)
        stream.close()

server = EchoServer()
server.listen(8888)
ioloop.IOLoop.instance().start()

這裏使用sleep( )函數,是模擬耗時操做。異步

sleep函數在tornado 4.1在gen模塊中有定義,能夠直接使用gen.sleep。socket


HTTPServer阻塞的請求版本async

import tornado.httpserver
import tornado.ioloop
import tornado.web
import base64
import socket


class MainHandler(tornado.web.RequestHandler):

    def get(self):
        s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
        s.connect(('localhost', 8888))
        s.send('hello\n')
        data = s.recv(4096)
        self.write(data)
        self.finish()

if __name__ == "__main__":
    app = tornado.web.Application(
        handlers = [ 
            (r"/", MainHandler)
        ]   
    )   

    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(8000)
    tornado.ioloop.IOLoop.instance().start()

這裏只是普通的socket請求,它是阻塞的。必須等2s後,服務器纔會返回數據。
tcp


HTTPServer異步的請求版本函數

import tornado.httpserver
import tornado.web
import socket
from tornado import ioloop
from tornado import gen 
from tornado.concurrent import Future

class MainHandler(tornado.web.RequestHandler):
    
    @gen.coroutine
    def get(self):
        data = yield self.get_data()
        self.write(data)
        self.finish()

    def get_data(self):
        future = Future()
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
        s.connect(('localhost', 8888))
        s.send('hello\n')

        def handle_data(sock, event):
            io_loop = ioloop.IOLoop.current()
            io_loop.remove_handler(sock)
            data = sock.recv(1024)
            future.set_result(data)

        io_loop = ioloop.IOLoop.current()
        io_loop.add_handler(s, handle_data, io_loop.READ)

        return future

if __name__ == "__main__":
    app = tornado.web.Application(
        handlers = [
            (r"/", MainHandler)
        ]
    )

    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(8000)
    tornado.ioloop.IOLoop.instance().start()


注意上面的異步函數get_data中,socket請求EchoServer,並非直接recv等待結果。而是先實例化一個新的Future對象,

而且將socket可讀事件登記到ioloop中。若是socket有數據時,就會回調handle_data,這裏會調用future.set_result( )方法。

get_data知足了上面的兩個條件:

  1. 返回Future對象

  2. 將future.set_result方法登記到ioloop中, 在結果返回時,就會調用。

最後注意gen.coroutine裝飾器,若是被裝飾的函數,出現異常,它是不會拋出的。須要訪問返回的future,才能知道。

舉例來講:

def handle_exce(future):
    if future.exception() is not None:
        print future.exec_info()

def func():
    future = async()
    future.add_done_callback(handle_exce)

@gen.coroutine   
def async():
    raise RuntimeError("async exception")


最後比較下性能,使用http_load測試:

這裏模擬200個用戶,不停的訪問10s。

阻塞版

./http_load -p 200 -s 10  url 
4 fetches, 200 max parallel, 20 bytes, in 10.0013 seconds
5 mean bytes/connection
0.399948 fetches/sec, 1.99974 bytes/sec
msecs/connect: 0.20175 mean, 0.277 max, 0.153 min
msecs/first-response: 5006.22 mean, 8010.14 max, 2002.34 min
HTTP response codes:
  code 200 -- 4


異步版

./http_load -p 200 -s 10  url 
800 fetches, 200 max parallel, 4000 bytes, in 10.0017 seconds
5 mean bytes/connection
79.9868 fetches/sec, 399.934 bytes/sec
msecs/connect: 20.0608 mean, 997.373 max, 0.03 min
msecs/first-response: 2020.97 mean, 2204.74 max, 2000.99 min
HTTP response codes:
  code 200 -- 800

能夠看到阻塞版的0.399948 fetches/sec和異步版的79.9853 fetches/sec。從中可見阻塞會大大影響tornado的性能。

好比耗時的數據庫查詢,耗時的運算。因此只有使用異步的庫,才能發揮tornado的高性能。

若是沒有相應的異步庫,能夠本身嘗試着寫。或者最簡單的,使用celery做爲異步任務隊列。celery有對應的tornado庫,tornado-celery。

相關文章
相關標籤/搜索