Tornado 多進(jìn)程實(shí)現(xiàn)分析詳解
引子
Tornado 是一個(gè)網(wǎng)絡(luò)異步的的web開(kāi)發(fā)框架, 并且可以利用多進(jìn)程進(jìn)行提高效率, 下面是創(chuàng)建一個(gè)多進(jìn)程 tornado 程序的例子.
#!/usr/bin/env python # -*- coding:utf-8 -*- import os import time import tornado.web import tornado.httpserver import tornado.ioloop import tornado.netutil import tornado.process class LongHandler(tornado.web.RequestHandler): def get(self): self.write(str(os.getpid())) time.sleep(10) if __name__ == "__main__": app = tornado.web.Application(([r'/', LongHandler], )) sockets = tornado.netutil.bind_sockets(8090) tornado.process.fork_processes(2) server = tornado.httpserver.HTTPServer(app) server.add_sockets(sockets) tornado.ioloop.IOLoop.instance().start()
上面代碼使用 tornado.process.fork_processes 創(chuàng)建了2個(gè)子進(jìn)程, 同時(shí)用時(shí)訪問(wèn)這個(gè) 服務(wù)兩次, 分別會(huì)返回兩個(gè)相鄰的pid. 可以看到 tornado 確實(shí)使用了兩個(gè)進(jìn)程來(lái)同時(shí)完成任務(wù).
我一直很好奇 tornado 是如何將請(qǐng)求調(diào)度到子進(jìn)程, 多個(gè)子進(jìn)程又如何不同時(shí)處理一個(gè)請(qǐng)求呢?
探究
我們首先是調(diào)用 tornado.netutil.bind_sockets 來(lái)創(chuàng)建一個(gè) socket(或一個(gè) socket 列表),
接著我們調(diào)用 tornado.process.fork_processes 來(lái) fork 子進(jìn)程, 閱讀此函數(shù)的代碼會(huì)發(fā)現(xiàn)這個(gè)函數(shù)僅僅是創(chuàng)建子進(jìn)程, 然后主進(jìn)程負(fù)責(zé)等待子進(jìn)程, 如果子進(jìn) 程退出則會(huì)根據(jù)條件重啟子進(jìn)程, 如果子進(jìn)程全部退出并不符合重啟條件,則主進(jìn)程退出.
調(diào)用這個(gè)函數(shù)之后, 子進(jìn)程中函數(shù)會(huì)返回, 子進(jìn)程則繼續(xù)執(zhí)行調(diào)用這個(gè)函數(shù)之后的代碼.
我們?cè)?fork 子進(jìn)程后做了如下操作.
server = tornado.httpserver.HTTPServer(app) server.add_sockets(sockets) tornado.ioloop.IOLoop.instance().start()
我們先看看 tornado.httpserver.HTTPServer.add_sockets 發(fā)現(xiàn) HTTPServer是繼承的 tornado.netutil.TCPServer , add_sockets 也是實(shí)現(xiàn)在 TCPServer 中
tornado.netutil.TCPServer.add_sockets
def add_sockets(self, sockets): if self.io_loop is None: self.io_loop = IOLoop.instance() for sock in sockets: self._sockets[sock.fileno()] = sock add_accept_handler(sock, self._handle_connection, io_loop=self.io_loop)
主要是映射了下 socket 和 socket 對(duì)應(yīng)的文件描述符, 我們看看它調(diào)用的 add_accept_handler
def add_accept_handler(sock, callback, io_loop=None): if io_loop is None: io_loop = IOLoop.instance() def accept_handler(fd, events): while True: try: connection, address = sock.accept() except socket.error as e: if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): return raise callback(connection, address) io_loop.add_handler(sock.fileno(), accept_handler, IOLoop.READ)
我們知道 I/O多路復(fù)用 在處理服務(wù)端 socket 時(shí), 當(dāng)有連接請(qǐng)求過(guò)來(lái)時(shí), 會(huì)觸發(fā) 可讀的事件, 此函數(shù)將 socket 在主事件循環(huán)中注冊(cè)讀事件(IOLoop.READ), 它的回調(diào) 會(huì)創(chuàng)建連接, 我注意到回調(diào)里的異常捕獲有這樣幾行
if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): return raise
發(fā)現(xiàn)在創(chuàng)建連接的時(shí)候會(huì)跳過(guò)這個(gè)異常呢, 為什么?那么 EWOULDBLOCK 和 EAGAIN是是什么呢? 通過(guò)查找知道它的意思是在非阻塞模式下, 不需要重讀或重寫, EAGAIN 是 EWOULDBLOCK 在 Windows 上的名字, 所以看到這里就很明確了.
結(jié)論
Tornado 多進(jìn)程的處理流程是先創(chuàng)建 socket, 然后再 fork 子進(jìn)程, 這樣所有的子進(jìn)程實(shí)際都監(jiān)聽(tīng) 一個(gè)(或多個(gè))文件描述符, 也就是都在監(jiān)聽(tīng)同樣的 socket.
當(dāng)連接過(guò)來(lái)所有的子進(jìn)程都會(huì)收到可讀事件, 這時(shí)候所有的子進(jìn)程都會(huì)跳到 accept_handler 回調(diào)函數(shù), 嘗試建立連接.
一旦其中一個(gè)子進(jìn)程成功的建立了連接, 當(dāng)其他子進(jìn)程再嘗試建立這個(gè)連接的時(shí)候就會(huì)觸發(fā) EWOULDBLOCK (或 EAGAIN) 錯(cuò)誤. 這時(shí)候回調(diào)函數(shù)判斷是這個(gè)錯(cuò)誤則返回函數(shù)不做處理.
當(dāng)成功建立連接的子進(jìn)程還在處理這個(gè)連接的時(shí)候又過(guò)來(lái)一個(gè)連接, 這時(shí)候就會(huì)有另外一個(gè) 子進(jìn)程接手這個(gè)連接.
Tornado 就是通過(guò)這樣一種機(jī)制, 利用多進(jìn)程提升效率, 由于連接只能由一個(gè)子進(jìn)程成功創(chuàng)建, 同一個(gè)請(qǐng)求也就不會(huì)被多個(gè)子進(jìn)程處理.
后記
寫完才發(fā)現(xiàn), 我所使用的代碼是 tornado-2.4.post2 版本, 當(dāng)前最新代碼是 3.3.0, 查看了下最新代碼, 最新代碼 TCPServer 寫到單獨(dú) tornado.tcpserver 里了, 其他和本文 相關(guān)的并沒(méi)有什么大的變化.
Category:PythonTagged:Pythonfork_processestornado多進(jìn)程web提升效率
以上就是本文關(guān)于Tornado 多進(jìn)程實(shí)現(xiàn)分析詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
python?pandas創(chuàng)建多層索引MultiIndex的6種方式
這篇文章主要為大家介紹了python?pandas創(chuàng)建多層索引MultiIndex的6種方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07python根據(jù)經(jīng)緯度計(jì)算距離示例
這篇文章主要介紹了python根據(jù)經(jīng)緯度計(jì)算距離示例, 計(jì)算兩點(diǎn)之間距離,需要的朋友可以參考下2014-02-02如何用六步教會(huì)你使用python爬蟲爬取數(shù)據(jù)
網(wǎng)絡(luò)爬蟲就是按照一定規(guī)則自動(dòng)訪問(wèn)互聯(lián)網(wǎng)上的信息并把內(nèi)容下載下來(lái)的程序或腳本,下面這篇文章主要給大家介紹了關(guān)于如何用六步教會(huì)你使用python爬蟲爬取數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2022-04-04PyCharm插件開(kāi)發(fā)實(shí)踐之PyGetterAndSetter詳解
這篇文章主要介紹了PyCharm插件開(kāi)發(fā)實(shí)踐-PyGetterAndSetter,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10windows下安裝python的C擴(kuò)展編譯環(huán)境(解決Unable to find vcvarsall.bat)
這篇文章主要介紹了windows下安裝python的C擴(kuò)展編譯環(huán)境(解決Unable to find vcvarsall.bat),需要的朋友可以參考下2018-02-02Python實(shí)現(xiàn)批量把SVG格式轉(zhuǎn)成png、pdf格式的代碼分享
這篇文章主要介紹了Python實(shí)現(xiàn)批量把SVG格式轉(zhuǎn)成png、pdf格式的代碼分享,本文代碼需要引用一個(gè)第三方模塊cairosvg,需要的朋友可以參考下2014-08-08在Python的Django框架中更新數(shù)據(jù)庫(kù)數(shù)據(jù)的方法
這篇文章主要介紹了在Python的Django框架中更新數(shù)據(jù)庫(kù)數(shù)據(jù),對(duì)此Django框架中提供了便利的插入和更新方法,需要的朋友可以參考下2015-07-07Python機(jī)器學(xué)習(xí)三大件之一numpy
這篇文章主要介紹了Python機(jī)器學(xué)習(xí)三大件之一numpy,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)python的小伙伴們有很好地幫助喲.需要的朋友可以參考下2021-05-05python使用tkinter打造三維繪圖系統(tǒng)的示例代碼
Python?的?tkinter?模塊是一個(gè)常用的?GUI(圖形用戶界面)工具包,它能夠讓你創(chuàng)建窗口應(yīng)用程序,你可以使用它來(lái)構(gòu)建用戶友好的界面,包括按鈕、標(biāo)簽、文本框、列表框等各種控件,本文講給大家介紹如何使用tkinter打造三維繪圖系統(tǒng),需要的朋友可以參考下2023-08-08