tornado源碼學習之ioloop

tornado版本: 4.4.1linux

tornado在linux下使用epoll解決併發,解決c10k問題。web

關於epoll瞭解能夠自行搜索瞭解。併發

tornado的epoll實現主要在tornado.ioloop裏面。app

咱們經過tornado的啓動流程學習和分析。socket

tornado的例子中啓動服務的流程:函數

import tornado.ioloop
import tornado.web


class MyHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.write("helloworld")


def make_app():
    return tornado.web.Application([
        (r"/", MyHandler),
    ])
if __name__ == "__main__":
    app = make_app()
    app.listen(port=9999)
    tornado.ioloop.IOLoop.current().start()

make_app會生成一個tornado的web.Application實例,這裏先不關心這個實例,接下來看app.listen方法:tornado

def listen(self, port, address="", **kwargs):
    from tornado.httpserver import HTTPServer
    server = HTTPServer(self, **kwargs)
    server.listen(port, address)
    return server

listen方法會建立一個httpserver,並調用這個server的listen:oop

def listen(self, port, address=""):
    sockets = bind_sockets(port, address=address)
    self.add_sockets(sockets)

def add_sockets(self, sockets):
    if self.io_loop is None:
        self.io_loop = IOLoop.current() # 這裏生成ioloop

    for sock in sockets:
        self._sockets[sock.fileno()] = sock
        add_accept_handler(sock, self._handle_connection,
                           io_loop=self.io_loop)

看到bind_sockets生成監聽的socket,而後調用add_sockets, 看到這裏會生成self.io_loop, IOLoop.current()方法的定義:學習

@staticmethod
def current(instance=True):
    current = getattr(IOLoop._current, "instance", None)
    if current is None and instance:
        return IOLoop.instance()
    return current

能夠看到,這裏用_current保證當前只有一個ioloop,一般,使用這個方法獲取ioloop。剛啓動時,這裏current爲None,因此會調用IOLoop.instance()code

@staticmethod
def instance():
    if not hasattr(IOLoop, "_instance"):
        with IOLoop._instance_lock:
            if not hasattr(IOLoop, "_instance"):
                # New instance after double check
                IOLoop._instance = IOLoop()
    return IOLoop._instance

仍是經過_instance獲取ioloop實例,沒有的話經過IOLoop()建立, 經過看IOLoop的實現,發現繼承於Configurable, 而Configurable重寫了__new__方法:

def __new__(cls, *args, **kwargs):
    base = cls.configurable_base()
    init_kwargs = {}
    if cls is base:
        impl = cls.configured_class()
        if base.__impl_kwargs:
            init_kwargs.update(base.__impl_kwargs)
    else:
        impl = cls
    init_kwargs.update(kwargs)
    instance = super(Configurable, cls).__new__(impl)
    # initialize vs __init__ chosen for compatibility with AsyncHTTPClient
    # singleton magic.  If we get rid of that we can switch to __init__
    # here too.
    instance.initialize(*args, **init_kwargs)
    return instance

能夠看到IOLoop()實際是經過configured_class方法建立的實例,而經過查看initialize的實現,只是將執行了IOLoop._current.instance = self,因此重點放到的Configurable的configured_class實現:

@classmethod
def configured_class(cls):
    # type: () -> type
    """Returns the currently configured class."""
    base = cls.configurable_base()
    if cls.__impl_class is None:
        base.__impl_class = cls.configurable_default()
    return base.__impl_class

能夠看到最後調用的仍是IOLoop的configurable_default,轉向IOLoop的configurable_default的實現:

@classmethod
def configurable_default(cls):
    if hasattr(select, "epoll"):
        from tornado.platform.epoll import EPollIOLoop
        return EPollIOLoop
    if hasattr(select, "kqueue"):
        # Python 2.6+ on BSD or Mac
        from tornado.platform.kqueue import KQueueIOLoop
        return KQueueIOLoop
    from tornado.platform.select import SelectIOLoop
    return SelectIOLoop

重點來了,這裏能夠看到,configurable_default是個工廠函數,方法中經過hasattr(select, "epoll")和hasattr(select, "kqueue")條件區分返回epoll,kqueue和select模型,即所說的tornado在linux下使用epoll模型,mac下使用kqueue,win下使用select模型。 咱們接着看EPollIOLoop的實現:

class EPollIOLoop(PollIOLoop):
    def initialize(self, **kwargs):
        super(EPollIOLoop, self).initialize(impl=select.epoll(), **kwargs)

能夠看到這裏初始化了epoll,而epoll的方法封裝都PollIOLoop中,因此在linux下,咱們tornado.ioloop.IOLoop.current()取到的就是EPollIOLoop實例 在PollIOLoop中,實現了add_handler, update_handler, remove_handler, start, stop方法,對應的epoll的各類操做。

如今返回到開始的httpserver建立listen方法這裏,在add_sockets方法中,初始化ioloop後,經過查看add_accept_handler方法:

def add_accept_handler(sock, callback, io_loop=None):
    if io_loop is None:
        io_loop = IOLoop.current()

    def accept_handler(fd, events):
        for i in xrange(_DEFAULT_BACKLOG):
            try:
                connection, address = sock.accept()
            except socket.error as e:
                # _ERRNO_WOULDBLOCK indicate we have accepted every
                # connection that is available.
                if errno_from_exception(e) in _ERRNO_WOULDBLOCK:
                    return
                # ECONNABORTED indicates that there was a connection
                # but it was closed while still in the accept queue.
                # (observed on FreeBSD).
                if errno_from_exception(e) == errno.ECONNABORTED:
                    continue
                raise
            callback(connection, address)
    io_loop.add_handler(sock, accept_handler, IOLoop.READ)

能夠看到調用的剛纔初始化的PollIOLoop實例的add_handler方法,將sockets放入到epoll監聽的套接字列表中。

最後調用tornado.ioloop.IOLoop.current().start()即PollIOLoop實例的start方法開始 執行epoll的poll

PollIOLoop中對epoll的使用詳細的閱讀源碼便可。

相關文章
相關標籤/搜索