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的抽象。看一下select.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ù),用來處理fd上的事件events,這里測(cè)試的fd為4,事件EPOLLIN。我們來跟蹤一下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()來包裹究竟是為了什么了?
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ù)用來修改fd感興趣的事件
以上就是python Tornado事件循環(huán)示例源碼解析的詳細(xì)內(nèi)容,更多關(guān)于python Tornado事件循環(huán)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Python自動(dòng)化Microsoft Excel和Word的操作方法
這篇文章主要介紹了使用Python自動(dòng)化Microsoft Excel和Word,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
python的簡(jiǎn)單web框架flask快速實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了python的簡(jiǎn)單web框架flask快速實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Python?Pywinauto輕松實(shí)現(xiàn)Windows桌面自動(dòng)化詳解
pywinauto這個(gè)Python庫可以讓你像魔法師一樣操控Windows?GUI,輕松模擬鼠標(biāo)鍵盤操作,自動(dòng)化Notepad,Excel甚至企業(yè)級(jí)軟件,下面小編就來和大家詳細(xì)介紹一下它的使用吧2025-05-05
Python Numpy:找到list中的np.nan值方法
今天小編就為大家分享一篇Python Numpy:找到list中的np.nan值方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-10-10
python實(shí)現(xiàn)小程序推送頁面收錄腳本
這篇文章主要介紹了python實(shí)現(xiàn)小程序推送頁面收錄腳本,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
python如何通過Json路徑返回Json響應(yīng)對(duì)應(yīng)的值
這篇文章主要介紹了python如何通過Json路徑返回Json響應(yīng)對(duì)應(yīng)的值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06

