但是對於普通的阻塞操作——比如MySql查詢,甚至是簡單的一句time.sleep--怎么讓其不阻塞呢?
回答這個問題首先要了解Tornado異步的原理。Tornado異步的核心是ioloop.py和iostream.py這兩個文件。ioloop.py實現了一個處理I/O事件的循環,iostream封裝了非阻塞的socket並把I/O事件注冊到ioloop上。Tornado的異步在linux平台基於epoll,它是基於事件而非輪詢的,這是其高效的原因(windows平台沒有epoll,tornado只能使用select,效率比epoll低)。
ps:tornado異步的原理我覺得沒有完全理解,但簡單的講就是這樣了。關於epoll和socket可以看這里。
知道原理后,再看這個問題。首先要說:Mysql查詢和time.sleep這兩個操作是不同類型的阻塞操作。
python進行Mysql查詢需要建立socket連接,這就可以通過iostream將其改為異步,但是現在通用的mysql庫使用c寫的python模塊,socket部分由c寫完並編譯,就不能修改了。如果是用python寫的mysql庫(如myconnpy),使用的是python的socket,是可以改為異步的庫。現在mongodb和pgsql都有異步的python驅動。
再說time.sleep,這種阻塞操作不需要socket,要將其改為異步,只能使用隊列,線程等方式。這里提供幾種方式:
第一種使用celery,它使用RabbitMQ做后端,使用隊列的方式實現異步,對應tornado有開源的tornado-celery,可以實現異步:
from celery import Celery import tcelery from tornado import gen.coroutine celery = Celery('tasks', backend='redis://localhost', broker='amqp://') @celery.task def test(strs): time.sleep(5) return strs class MainHandler(tornado.web.RequestHandler): @gen.coroutine def get(self): result = yield tcelery.celery_task(test, "hello world") self.write("%s" % result )
第二種使用concurrent.futures,這個並發庫在python3自帶 ,在python2需要安裝sudo pip install futures
。這種方法是使用線程池或者進程池的方式實現異步的,它有ThreadPoolExecutor
和ProcessPoolExecutor
,要求Handler類要有一個名為executor
的屬性,指向一個Executor對象,代碼如下:
from concurrent.futures import ThreadPoolExecutor class SleepHandler(tornado.web.RequestHandler): executor = ThreadPoolExecutor(2) @tornado.web.asynchronous @tornado.gen.coroutine def get(self): res = yield self.sleep() self.write("sleep %s s" % res) self.finish() @run_on_executor def sleep(self): time.sleep(5) return 5
上面ThreadPoolExecutor(2),建立了size為2的一個線程池。
這樣SleepHandler就不會阻塞其他Handler。