Tornado 4.3 文檔翻譯: 用戶指南-異步和非阻塞I/O
譯者說
Tornado 4.3
于2015年11月6日發布,該版本正式支持Python3.5
的async
/await
關鍵字,并且用舊版本CPython編譯Tornado同樣可以使用這兩個關鍵字,這無疑是一種進步。其次,這是最后一個支持Python2.6
和Python3.2
的版本了,在后續的版本了會移除對它們的兼容。現在網絡上還沒有Tornado4.3
的中文文檔,所以為了讓更多的朋友能接觸并學習到它,我開始了這個翻譯項目,希望感興趣的小伙伴可以一起參與翻譯,項目地址是tornado-zh on Github,翻譯好的文檔在Read the Docs上直接可以看到。歡迎Issues or PR。
異步和非阻塞I/O
實時web功能需要為每個用戶提供一個多數時間被閑置的長連接,在傳統的同步web服務器中,這意味著要為每個用戶提供一個線程,當然每個線程的開銷都是很昂貴的.
為了盡量減少并發連接造成的開銷,Tornado使用了一種單線程事件循環的方式.這就意味著所有的應用代碼都應該是異步非阻塞的,因為在同一時間只有一個操作是有效的.
異步和非阻塞是非常相關的并且這兩個術語經常交換使用,但它們不是完全相同的事情.
阻塞
一個函數在等待某些事情的返回值的時候會被 阻塞. 函數被阻塞的原因有很多:網絡I/O,磁盤I/O,互斥鎖等.事實上 每個 函數在運行和使用CPU的時候都或多或少會被阻塞(舉個極端的例子來說明為什么對待CPU阻塞要和對待一般阻塞一樣的嚴肅: 比如密碼哈希函數bcrypt, 需要消耗幾百毫秒的CPU時間,這已經遠遠超過了一般的網絡或者磁盤請求時間了).
一個函數可以在某些方面阻塞在另外一些方面不阻塞.例如, tornado.httpclient
在默認的配置下,會在DNS解析上面阻塞,但是在其他網絡請求的時候不阻塞(為了減輕這種影響,可以用 ThreadedResolver
或者是通過正確配置 libcurl
用 tornado.curl_httpclient
來做).在Tornado的上下文中,我們一般討論網絡I/O上下文的阻塞,盡管各種阻塞已經被最小化了.
異步
異步 函數在會在完成之前返回,在應用中觸發下一個動作之前通常會在后臺執行一些工作(和正常的 同步 函數在返回前就執行完所有的事情不同).這里列舉了幾種風格的異步接口:
- 回調參數
- 返回一個占位符 (
.Future
,Promise
,Deferred
) - 傳送給一個隊列
- 回調注冊表 (POSIX信號)
不論使用哪種類型的接口, 按照定義 異步函數與它們的調用者都有著不同的交互方式;也沒有什么對調用者透明的方式使得同步函數異步(類似 gevent使用輕量級線程的系統性能雖然堪比異步系統,但它們并沒有真正的讓事情異步).
例子
一個簡單的同步函數:
from tornado.httpclient import HTTPClient def synchronous_fetch(url): http_client = HTTPClient() response = http_client.fetch(url) return response.body
把上面的例子用回調參數重寫的異步函數:
from tornado.httpclient import AsyncHTTPClient def asynchronous_fetch(url, callback): http_client = AsyncHTTPClient() def handle_response(response): callback(response.body) http_client.fetch(url, callback=handle_response)
使用 Future
代替回調:
from tornado.concurrent import Future def async_fetch_future(url): http_client = AsyncHTTPClient() my_future = Future() fetch_future = http_client.fetch(url) fetch_future.add_done_callback( lambda f: my_future.set_result(f.result())) return my_future
Future
版本明顯更加復雜,但是 Futures
卻是Tornado中推薦的寫法.因為它有兩個主要的優勢.首先是錯誤處理更加一致,因為 Future.result
方法可以簡單的拋出異常(相較于常見的回調函數接口特別指定錯誤處理),而且 Futures
很適合和協程一起使用.協程會在后面深入討論.這里是上面例子的協程版本,和最初的同步版本很像:
from tornado import gen @gen.coroutine def fetch_coroutine(url): http_client = AsyncHTTPClient() response = yield http_client.fetch(url) raise gen.Return(response.body)
raise gen.Return(response.body)
聲明是在Python 2 (and 3.2)下人為執行的, 因為在其中生成器不允許返回值.為了克服這個問題,Tornado的協程拋出一種特殊的叫 Return
的異常. 協程捕獲這個異常并把它作為返回值.在Python 3.3和更高版本,使用 return response.body
有相同的結果.