Tornado實(shí)現(xiàn)多進(jìn)程/多線程的HTTP服務(wù)詳解
用tornado web服務(wù)的基本流程
1.實(shí)現(xiàn)處理請求的Handler,該類繼承自tornado.web.RequestHandler,實(shí)現(xiàn)用于處理請求的對應(yīng)方法如:get、post等。返回內(nèi)容用self.write方法輸出。
2.實(shí)例化一個(gè)Application。構(gòu)造函數(shù)的參數(shù)是一個(gè)Handlers列表,通過正則表達(dá)式,將請求與Handler對應(yīng)起來。通過dict將Handler需要的其他對象以參數(shù)的方式傳遞給Handler的initialize方法。
3.初始化一個(gè)tornado.httpserver.HTTPServer對象,構(gòu)造函數(shù)的參數(shù)是上一步的Application對象。
4.為HTTPServer對象綁定一個(gè)端口。
5.開始IOLoop。
需要用到的特性
由于tornado的亮點(diǎn)是異步請求,所以這里首先想到的是將所有請求都改造為異步的。但是這里遇到一個(gè)問題,就是異步函數(shù)內(nèi)一定不能有阻塞調(diào)用出現(xiàn),否則整個(gè)IOLoop都會(huì)被卡住。這就要求徹底地去改造服務(wù),將所有IO或是用時(shí)較長的請求都改造為異步函數(shù)。這個(gè)工程量是非常大的,需要去修改已有的代碼。因此,我們考慮用線程池的方式去實(shí)現(xiàn)。當(dāng)一個(gè)線程阻塞在某個(gè)請求或IO時(shí),其他線程或IOLoop會(huì)繼續(xù)執(zhí)行。
另外一個(gè)瓶頸就是GIL限制了CPU的并發(fā)數(shù)量,因此考慮用子進(jìn)程的方式增加進(jìn)程數(shù),提高服務(wù)能力上限。
綜合上面的分析,大致用以下方案:
- 通過子進(jìn)程的方式復(fù)制多個(gè)進(jìn)程,使子進(jìn)程中的只讀頁指向同一個(gè)物理頁。
- 線程池?;乇墚惒礁脑斓墓ぷ髁?,增加IO的并發(fā)量。
測試代碼
首先測試線程池,測試用例為:
對sleep頁面同時(shí)發(fā)出兩個(gè)請求:
- 在線程池中運(yùn)行的函數(shù)(這里是self.block_task)能夠同時(shí)執(zhí)行。表現(xiàn)為在控制臺(tái)交替打印出數(shù)字。
- 兩個(gè)get請求幾乎同時(shí)返回,在瀏覽器上顯示返回的內(nèi)容。
線程池的測試代碼如下:
import os
import sys
import time
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
from tornado.options import define, options
class HasBlockTaskHandler(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(20) #起線程池,由當(dāng)前RequestHandler持有
@tornado.gen.coroutine
def get(self):
strTime = time.strftime("%Y-%m-%d %H:%M:%S")
print "in get before block_task %s" % strTime
result = yield self.block_task(strTime)
print "in get after block_task"
self.write("%s" % (result))
@run_on_executor
def block_task(self, strTime):
print "in block_task %s" % strTime
for i in range(1, 16):
time.sleep(1)
print "step %d : %s" % (i, strTime)
return "Finish %s" % strTime
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/sleep", HasBlockTaskHandler)], autoreload=False, debug=False)
http_server = tornado.httpserver.HTTPServer(app)
http_server.bind(8888)
tornado.ioloop.IOLoop.instance().start()
整個(gè)代碼里有幾個(gè)位置值得關(guān)注:
1.executor = ThreadPoolExecutor(20)。這是給Handler類初始化了一個(gè)線程池。其中concurrent.futures不屬于tornado,是python的一個(gè)獨(dú)立模塊,在python3中是內(nèi)置模塊,python2.7需要自己安裝。
2.修飾符@run_on_executor。這個(gè)修飾符將同步函數(shù)改造為在executor(這里是線程池)上運(yùn)行的異步函數(shù),內(nèi)部實(shí)現(xiàn)是將被修飾的函數(shù)submit到executor,返回一個(gè)Future對象。
3.修飾符@tornado.gen.coroutine。被這個(gè)修飾符修飾的函數(shù),是一個(gè)以同步函數(shù)方式編寫的異步函數(shù)。原本通過callback方式編寫的異步代碼,有了這個(gè)修飾符,可以通過yield一個(gè)Future的方式來寫。被修飾的函數(shù)在yield了一個(gè)Future對象后將會(huì)被掛起,F(xiàn)uture對象的結(jié)果返回后繼續(xù)執(zhí)行。
運(yùn)行代碼后,在兩個(gè)不同瀏覽器上訪問sleep頁面,得到了想要的效果。這里有一個(gè)小插曲,就是如果在同一瀏覽器的兩個(gè)tab上進(jìn)行測試,是無法看到想要的效果。第二個(gè)get請求會(huì)被block,直到第一個(gè)get請求返回,服務(wù)端才開始處理第二個(gè)get請求。這讓我一度覺得多線程沒有生效,用了半天時(shí)間查了很多資料,才看到是瀏覽器把相同的第二個(gè)請求block了,具體鏈接參考這里。
由于tornado很方便地支持多進(jìn)程模型,多進(jìn)程的使用要簡單很多,在以上例子中,只需要對啟動(dòng)部分稍作改動(dòng)即可。具體代碼如下所示:
if __name__ == "__main__": tornado.options.parse_command_line() app = tornado.web.Application(handlers=[(r"/sleep", HasBlockTaskHandler)], autoreload=False, debug=False) http_server = tornado.httpserver.HTTPServer(app) http_server.bind(8888) print tornado.ioloop.IOLoop.initialized() http_server.start(5) tornado.ioloop.IOLoop.instance().start()
需要注意的地方有兩點(diǎn):
1.app = tornado.web.Application(handlers=[(r"/sleep", HasBlockTaskHandler)], autoreload=False, debug=False),在生成Application對象時(shí),要將autoreload和debug兩個(gè)參數(shù)至為False。也就是需要保證在fork子進(jìn)程之前IOLoop是未被初始化的。這個(gè)可以通過tornado.ioloop.IOLoop.initialized()函數(shù)來跟。
2.http_server.start(5)在啟動(dòng)IOLoop之前通過start函數(shù)設(shè)置進(jìn)程數(shù)量,如果設(shè)置為0表示每個(gè)CPU都啟動(dòng)一個(gè)進(jìn)程。
最后的效果是可以看到n+1個(gè)進(jìn)程在運(yùn)行,且公用同一個(gè)端口。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
windows環(huán)境下tensorflow安裝過程詳解
這篇文章主要為大家詳細(xì)介紹了windows環(huán)境下tensorflow安裝過程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
python如何利用matplotlib繪制并列雙柱狀圖并標(biāo)注數(shù)值
Python之中最好的圖表庫叫matplotlib,matplotlib,顧名思義就是提供了一整套和matlab相似的API,它的文檔相當(dāng)完備,下面這篇文章主要給大家介紹了關(guān)于python如何利用matplotlib繪制并列雙柱狀圖并標(biāo)注數(shù)值的相關(guān)資料,需要的朋友可以參考下2022-04-04
python 基于pygame實(shí)現(xiàn)俄羅斯方塊
這篇文章主要介紹了python 基于pygame實(shí)現(xiàn)俄羅斯方塊的方法,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-03-03
python3 破解 geetest(極驗(yàn))的滑塊驗(yàn)證碼功能
這篇文章主要介紹了python3 破解 geetest(極驗(yàn))的滑塊驗(yàn)證碼功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-02-02
詳解使用Python寫一個(gè)向數(shù)據(jù)庫填充數(shù)據(jù)的小工具(推薦)
這篇文章主要介紹了用Python寫一個(gè)向數(shù)據(jù)庫填充數(shù)據(jù)的小工具,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
淺析Python中正則表達(dá)式函數(shù)search()和match()的使用
在Python中,正則表達(dá)式是處理字符串的強(qiáng)大工具,search()和match()是Python標(biāo)準(zhǔn)庫中re模塊中兩個(gè)常用的正則表達(dá)式方法,本文將詳細(xì)講解這兩個(gè)方法的使用,需要的可以參考一下2023-08-08

