tornado源码学习之ioloop

tornado版本: 4.4.1

tornado在linux下使用epoll解决并发,解决c10k问题。

关于epoll了解可以自行搜索了解。

tornado的epoll实现主要在tornado.ioloop里面。

我们通过tornado的启动流程学习和分析。

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方法:

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:

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()

@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的使用详细的阅读源码即可。

猜你喜欢

转载自my.oschina.net/u/2299936/blog/854380
今日推荐