python Tornado事件循環(huán)示例源碼解析
hello world
#!/usr/bin/env python import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web from tornado.options import define, options define("port", default=8888, help="run on the given port", type=int) class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") def main(): tornado.options.parse_command_line() application = tornado.web.Application([ (r"/", MainHandler), ]) http_server = tornado.httpserver.HTTPServer(application) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__": main()
tornado提供了高效的異步機(jī)制,我們先不管Application實(shí)例化過程,以及http_server創(chuàng)建socket、bind、listen的過程,直接調(diào)IOLoop.instance().start進(jìn)行源碼分析。
IOLoop.instance()
@classmethod def instance(cls): """Returns a global IOLoop instance. Most single-threaded applications have a single, global IOLoop. Use this method instead of passing around IOLoop instances throughout your code. A common pattern for classes that depend on IOLoops is to use a default argument to enable programs with multiple IOLoops but not require the argument for simpler applications: class MyClass(object): def __init__(self, io_loop=None): self.io_loop = io_loop or IOLoop.instance() """ if not hasattr(cls, "_instance"): cls._instance = cls() return cls._instance
顯然是一個(gè)單例模式,注意tornado中的注釋,大多數(shù)單線程只能有一個(gè)ioloop。
start()
這個(gè)函數(shù)將開始事件循環(huán)。
def start(): """ Starts the I/O loop. The loop will run until one of the I/O handlers calls stop(), which will make the loop stop after the current event iteration completes. """ # 判斷是否設(shè)置了,如果是,將直接退出。 if self._stopped: self._stopped = False return self._running = True while True: # Never use an infinite timeout here - it can stall epoll # 設(shè)置輪詢時(shí)間 poll_timeout = 0.2 # Prevent IO event starvation by delaying new callbacks # to the next iteration of the event loop. callbacks = self._callbacks self._callbacks = [] # 及時(shí)調(diào)用回調(diào)函數(shù) for callback in callbacks: self._run_callback(callback) if self._callbacks: poll_timeout = 0.0 # 如果設(shè)置了超時(shí)時(shí)間 if self._timeouts: # 獲取當(dāng)前時(shí)間 now = time.time() while self._timeouts and self._timeouts[0].deadline <= now: timeout = self._timeouts.pop(0) self._run_callback(timeout.callback) if self._timeouts: milliseconds = self._timeouts[0].deadline - now poll_timeout = min(milliseconds, poll_timeout) # 再一次檢查事件循環(huán)是否在運(yùn)行 if not self._running: break # 目前不清楚作用 if self._blocking_signal_threshold is not None: # clear alarm so it doesn't fire while poll is waiting for # events. signal.setitimer(signal.ITIMER_REAL, 0, 0) try: # 開始等待事件發(fā)生 # _impl初始化和poll源代碼見下面 event_pairs = self._impl.poll(poll_timeout) except Exception, e: # Depending on python version and IOLoop implementation, # different exception types may be thrown and there are # two ways EINTR might be signaled: # * e.errno == errno.EINTR # * e.args is like (errno.EINTR, 'Interrupted system call') if (getattr(e, 'errno', None) == errno.EINTR or (isinstance(getattr(e, 'args', None), tuple) and len(e.args) == 2 and e.args[0] == errno.EINTR)): continue else: raise if self._blocking_signal_threshold is not None: signal.setitimer(signal.ITIMER_REAL, self._blocking_signal_threshold, 0) # Pop one fd at a time from the set of pending fds and run # its handler. Since that handler may perform actions on # other file descriptors, there may be reentrant calls to # this IOLoop that update self._events self._events.update(event_pairs) while self._events: fd, events = self._events.popitem() try: # 見下面的分析 self._handlers[fd](fd, events) except (KeyboardInterrupt, SystemExit): raise except (OSError, IOError), e: if e.args[0] == errno.EPIPE: # Happens when the client closes the connection pass else: logging.error("Exception in I/O handler for fd %d", fd, exc_info=True) except: logging.error("Exception in I/O handler for fd %d", fd, exc_info=True) # reset the stopped flag so another start/stop pair can be issued self._stopped = False if self._blocking_signal_threshold is not None: signal.setitimer(signal.ITIMER_REAL, 0, 0)
我們看看輸入localhost:8888,event_pairs
的值:
可以看出,event_pairs是一個(gè)元組列表,其中第一個(gè)成員4為accept套接字值,1表示為事件類型。我們看看事件類型為:
_EPOLLIN = 0x001 _EPOLLPRI = 0x002 _EPOLLOUT = 0x004 _EPOLLERR = 0x008 _EPOLLHUP = 0x010 _EPOLLRDHUP = 0x2000 _EPOLLONESHOT = (1 << 30) _EPOLLET = (1 << 31) NONE = 0 READ = _EPOLLIN WRITE = _EPOLLOUT ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP
可見上述,是文件描述符4,可讀事件發(fā)生。
self._impl
我們跟蹤self._impl初始化過程,可以看到事件循環(huán)核心epoll是如何被使用的。在IOLoop實(shí)例化開始:
class IOLoop(object): def __init__(self, impl = None): self._impl = impl or _poll # Choose a poll implementation. Use epoll if it is available, fall back to # select() for non-Linux platforms # hasattr(object, attrname)表示某個(gè)對(duì)象中是否包含屬性 if hasattr(select, "epoll"): # Python 2.6+ on Linux # 在linux上使用的是select.epoll _poll = select.epoll elif hasattr(select, "kqueue"): # Python 2.6+ on BSD or Mac _poll = _KQueue else: try: # Linux systems with our C module installed import epoll _poll = _EPoll except: # All other systems import sys if "linux" in sys.platform: logging.warning("epoll module not found; using select()") _poll = _Select
從上面的代碼中,可以看到,_poll是對(duì)于多個(gè)平臺(tái)下epoll、_kQueue的抽象??匆幌聅elect.epoll下的返回結(jié)果:其返回對(duì)象是一個(gè)邊沿觸發(fā)的polling對(duì)象,當(dāng)然也可以用作水平觸發(fā)。
返回的select.epoll對(duì)象的方法:
- epoll.close() 關(guān)閉epoll fd文件描述符
- epoll.fileno() 返回epoll fd文件描述符只
- epoll.register(fd, eventmask) 注冊(cè)fd某個(gè)事件
- epoll.poll([timeout = -1, maxevents = -1]) wait for events. timeout in seconds
self._handlers[fd](fd, events)
顯然,self._handlers[fd]是返回一個(gè)回調(diào)函數(shù),用來(lái)處理fd上的事件events,這里測(cè)試的fd為4,事件EPOLLIN。我們來(lái)跟蹤一下self._handlers變化過程??纯丛贗OLoop初始化的過程。
def __init__(self): self._handles = {} .... if os.name != 'nt': r, w = os.pipe() self._set_nonblocking(r) self._set_nonblocking(w) ..... self._waker_reader = os.fdopen(r, "rb", 0) self._waker_writer = os.fdopen(w, "wb", 0) # 顯然這是對(duì)讀管道文件描述符事件處理函數(shù) self.add_handler(r, self._read_waker, self.READ)
add_handler(self, fd, handler, events)
def add_handler(self, fd, handler, events): self._handlers[fd] = stack_context.wrap(handler) self._impl.register(fd, events| self.ERROR)
可見add_handler干了兩件事情:
- 回調(diào)函數(shù)設(shè)置,當(dāng)然不僅僅是簡(jiǎn)單的將handler賦值,而是使用了stack_context.wrap包裹了該函數(shù),具體實(shí)現(xiàn),見下面。
- epoll對(duì)象添加該事件,就是在代碼的第二行。所以self._handlers[fd](fd, args)實(shí)際上就是設(shè)置的回調(diào)函數(shù)。那么用stack_context.wrap()來(lái)包裹究竟是為了什么了?
update_handler(self, fd, events)
def update_handler(self, fd, events): """Changes the events we listen for fd.""" self._impl.modify(fd, events | self.ERROR)
該函數(shù)用來(lái)修改fd感興趣的事件
以上就是python Tornado事件循環(huán)示例源碼解析的詳細(xì)內(nèi)容,更多關(guān)于python Tornado事件循環(huán)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 簡(jiǎn)單理解Python中的事件循環(huán)EventLoop
- Python?異步之如何啟動(dòng)獲取事件循環(huán)
- 在Python的一段程序中如何使用多次事件循環(huán)詳解
- python html2text庫(kù)將HTML文檔轉(zhuǎn)換為純文本格式使用示例探索
- python?Prophet時(shí)間序列預(yù)測(cè)工具庫(kù)使用功能探索
- Python flashtext文本搜索和替換操作庫(kù)功能使用探索
- python ftfy庫(kù)處理金融方面文件編碼錯(cuò)誤實(shí)例詳解
- python uvloop事件循環(huán)庫(kù)使用功能示例探究
相關(guān)文章
Python操作redis實(shí)例小結(jié)【String、Hash、List、Set等】
這篇文章主要介紹了Python操作redis的常見方法,結(jié)合實(shí)例形式總結(jié)分析了Python redis操作中String、Hash、List、Set等相關(guān)操作函數(shù)與使用技巧,需要的朋友可以參考下2019-05-05Python如何把十進(jìn)制數(shù)轉(zhuǎn)換成ip地址
這篇文章主要介紹了Python如何把十進(jìn)制數(shù)轉(zhuǎn)換成ip地址,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05使用Python簡(jiǎn)單編寫一個(gè)股票監(jiān)控系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了如何使用Python簡(jiǎn)單編寫一個(gè)股票監(jiān)控系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12python神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)利用PyTorch進(jìn)行回歸運(yùn)算
這篇文章主要為大家介紹了python神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)利用PyTorch進(jìn)行回歸運(yùn)算的實(shí)現(xiàn)代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Python實(shí)現(xiàn)圖片識(shí)別加翻譯功能
這篇文章主要介紹了Python使用百度AI接口實(shí)現(xiàn)圖片識(shí)別加翻譯功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12python pandas 解析(讀取、寫入)CSV 文件的操作方法
這篇文章主要介紹了python pandas 解析(讀取、寫入) CSV 文件,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12Python?OpenCV超詳細(xì)講解透視變換的實(shí)現(xiàn)
OpenCV用C++語(yǔ)言編寫,它具有C?++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac?OS,OpenCV主要傾向于實(shí)時(shí)視覺應(yīng)用,并在可用時(shí)利用MMX和SSE指令,本篇文章帶你通過OpenCV實(shí)現(xiàn)透視變換2022-04-04