tornado原始碼解析之IOLoop

發表於2016-08-15

0. 簡介

tornado是一個用Python語言寫成的Web伺服器兼Web應用框架,由FriendFeed公司在自己的網站FriendFeed中使用,被Facebook收購以後框架以開源軟體形式開放給大眾。

tornado最大的特點就是其支援非同步IO,所以它有著優異的效能。下表是和一些其他Web框架與伺服器的對比:(處理器為 AMD Opteron, 主頻2.4GHz, 4核) (來源wikipedia)

服務 部署 請求/每秒
Tornado nginx, 4程式 8213
Tornado 1個單執行緒程式 3353
Django Apache/mod_wsgi 2223
web.py Apache/mod_wsgi 2066
CherryPy 獨立 785

先來看看hello world的例子。^_^

執行:

我們就得到一個web server監聽在8888埠。用curl命令get一下,就返回了”Hello, world”。

tornado的程式碼結構可以在其官網瞭解,本文著重分析IOLoop的實現。

1. IOLoop

1.1 http互動的大致過程

介紹IOLoop之前我們先看看http server和http client互動的一個大致過程。

tornado原始碼解析之IOLoop

server端監聽在某個埠,client端傳送請求過來,server處理後返回,然後繼續等待下一個請求,周而復始。如果用socket那一坨來描述的話就是:

1.2 聊聊阻塞與非阻塞

所謂阻塞,就是程式正在等待某些資源(如IO),而處於等待執行的狀態(不佔用CPU資源)。比如connect((“google.com”, 80))返回之前程式都是阻塞的,在它下面的語句得不到執行,除非connect返回。

很顯然阻塞式的IO模型有個缺點就是併發量不大,試想如果server程式在do_something()處阻塞,而這時另外有個客戶端試圖連進來,則可能得不到響應。

提高併發量有幾種實現方式:多執行緒(一個連線fork一個執行緒去處理);多程式(一個連線fork一個子程式去處理)(apache);事件驅動(nginx, epoll)等。tornado就是基於epoll(Linux)事件驅動模型實現的。

當然它們有各自的優缺點,此文不詳述,有興趣的讀者可以自行google之。^_^

關於IO模型,epoll, 同步,非同步,阻塞,非阻塞的概念,可以參考這兩篇文章:
https://segmentfault.com/a/11…

http://blog.csdn.net/historya…

1.3 IOLoop實現

1.3.1 IOLoop配置

前文說到tornado是基於epoll事件驅動模型,也不完全正確,tornado實際上是根據平臺選擇底層驅動。請看IOLoop類的configurable_default方法:

這裡的IOLoop實際上是個通用介面,根據不同平臺選擇:linux->epoll,BSD->kqueue,如果epoll和kqueue都不支援則選擇select(效能要差些)。

class IOLoop(Configurable):IOLoop繼承了Configurable類,Configurable類的__new__方法呼叫了configured_class方法:

configured_class方法又呼叫了configurable_default方法:

所以當初始化一個IOLoop例項的時候就給IOLoop做了配置,根據不同平臺選擇合適的驅動。

1.3.2 IOLoop例項化

下面我們來看IOLoop的例項化函式:

很顯然,這裡是實現了一個全域性的單例模式。確保多個執行緒也只有一個IOLoop例項。(思考一下:為什要double check?if not hasattr(IOLoop, "_instance") ^_^)

1.3.3 實現epoll的介面(假設是在Linux平臺)

我們知道epoll支援3種操作:

分別對應tornado.IOLoop裡面的三個函式:add_handler, remove_handler, update_handler

下面看看這三個函式:

這裡的self._impl就是select.epoll(),使用方法可以參考epoll介面。

1.3.4 事件驅動模型的大致思路

IOLoop的start()方法用於啟動事件迴圈(Event Loop)。

大致的思路是:有連線進來(client端請求),就丟給epoll,順便註冊一個事件和一個回撥函式,我們主執行緒還是繼續監聽請求;然後在事件迴圈中,如果發生了某種事件(如socket可讀,或可寫),則呼叫之前註冊的回撥函式去處理。這和Node.js的思路是一致的。

1.3.5 關於cpu bound任務

tornado很適合處理IO bound的任務,如果遇到cpu bound的任務,則還是會阻塞整個程式。這個時候就必須將耗時的任務丟到另一個worker,或者佇列中去處理(如celery)。

1.3.6 其他

IOLoop類還有其他一些方法,多為輔助函式,讀者可以自行參考,此處不詳述。

行文比較草率,如有錯誤和不足之處,敬請指正。

下次繼續分析tornado其他模組。^_^

相關文章