[转]tornado ioloop start 的过程

解决了我一个疑问的一篇文章

转载自kaka_ace’s blog

本文链接地址: Tornado底层学习 (1) — tornado ioloop start 的过程

每一个 tornado 应用都会把 tornado ioloop 导入到代码中, 通过 ioloop 事件触发
机制, 处理 http request, 或者其他的协议的连接消息. tornado 在 Linux 系统中优先
使用 epoll 的封装, 基于 epoll 做事件处理.

现在写一个简短的 httpserver 小程序:
调试环境:
* IDE: Pycharm 4.0
* 系统: Ubuntu 14.04
* Python 版本: 3.4.0
* tornado 库版本: 4.0.2

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# @file          t_tornado.py
# @author   kaka_ace 
# @date       Jan 25 2015
# @breif     
# 

import tornado.auth
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web


class BaseHandler(tornado.web.RequestHandler):
    pass 


class HelloHandler(BaseHandler):
    def post(self):
        return


class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/hello", HelloHandler),
        ]
        settings = dict(
            #debug=True,  #  debug 设置 True 时, Application.__init__ 调用 tornado.autoreload.start()
        )
        tornado.web.Application.__init__(self, handlers, **settings)


def main():
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(8080)

    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()

不到 50 行的应用代码, 抛去其他的业务逻辑, 调试和阅读 ioloop start 之前
所做的工作.

先展示 ioloop.py 里 class 关系图:
[转]tornado ioloop start 的过程_第1张图片
IOLoop 是 PollIOLoop 的基类, 而下面将会讲解 PollIOLoop 如何调用真正的平台自己的
io 库.

现在我们分析 main() 函数中 三个语句:
1: http_server = tornado.httpserver.HTTPServer(Application())
2: http_server.listen(8080)
3: tornado.ioloop.IOLoop.instance().start()

语句1 解析: HTTPServer init 调用时, ioloop 参数默认是 None,
HTTPServer 定义在 httpserver.py 继承 (TCPServer, httputil.HTTPServerConnectionDelegate)
tornado.web.Application 定义在 web.py 继承 httputil.HTTPServerConnectionDelegate

Application() 作为 request_callback 参数传入 HTTPServer init

语句2 解析: listen(8080)
listen源码:

def listen(self, port, address=""):
        """Starts accepting connections on the given port.
        ... 忽略 
        """
        sockets = bind_sockets(port, address=address)
        self.add_sockets(sockets)

    def add_sockets(self, sockets):
        """Makes this server start accepting connections on the given sockets.
       ... 
        """
        if self.io_loop is None:
           # listen 操作时即生成了 ioloop   
            self.io_loop = IOLoop.current()

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

调用 add_sockets 函数中, 因为 ioloop 初始为 None, 因此调用 self.io_loop = IOLoop.current()
当前线程中 获取 ioloop 引用, 语句3里 instance() 操作中就没有必要再生成新的 ioloop类了,
因为 一个线程中 仅保留有一个 ioloop 单例类.

那么开始重点分析 ioloop实例的生成过程:
观察 IOLoop.current()的调用:

@staticmethod
    def current():
        """Returns the current thread's `IOLoop`.
        ... 省略注释说明
        """
        # 先判断单例 "instance" 属性是否存在
        current = getattr(IOLoop._current, "instance", None)
        if current is None:
            return IOLoop.instance()
        return current

    # instance 定义 
    @staticmethod
    def instance():
        """Returns a global `IOLoop` instance.
        ... 省略注释说明 
        """
        if not hasattr(IOLoop, "_instance"):
            # 利用 Python with 语句来封装线程锁的上下文操作 , 可以省去 UnLock 代码  
            with IOLoop._instance_lock:
                if not hasattr(IOLoop, "_instance"):
                    # New instance after double check
                    IOLoop._instance = IOLoop()
        return IOLoop._instance  

我们调试单步挨个语句到 instance() 函数时, 第一次使用实例需要创建,
因此调用 with IOLoop._instance_lock: (instance 的代码是 Python 单例类
实现的很好的代码例子, 使用到了 threading.Lock() )

IOLoop 继承了 Configurable 类(声明在 util.py), 并没有显示定义 init(),
而是在实例类创建的过程中, 重写了 new() 函数, 显示地操作 IOLoop 实例创建的行文.

再阅读 Configurable 类 new 函数的定义:

def __new__(cls, **kwargs):
        # cls 是 Configurable 派生出的 IOLoop 引用 
        base = cls.configurable_base()  # IOLoop 重写该函数, 返回 IOLoop class 自身类引用  
        args = {}
        if cls is base:  
            # 调用了 cls.configurable_default(), 在 IOLoop 见下一段的代码例子
            # impl 此时即 EPollIOLoop (Linux 系统中调试)
            impl = cls.configured_class() 
            if base.__impl_kwargs:
                args.update(base.__impl_kwargs)
        else:
            impl = cls
        args.update(kwargs)

        # 此时 tornado 利用了 Python __new__ magic function 干预了 IOLoop 的实例生成, 
        # 而真正的 IOLoop 类实际是派生类  EPollIOLoop
        # instance 是 EPollIOLoop 的实例类. tornado 根据系统平台,统一使用基类
        # IOLoop 作为对外接口提供给开发者, 隐藏了平台底层实际的对象.
        instance = super(Configurable, cls).__new__(impl)
        # initialize vs __init__ chosen for compatiblity with AsyncHTTPClient
        # singleton magic.  If we get rid of that we can switch to __init__
        # here too.
        instance.initialize(**args)
        return instance 

IOLoop configurable_base 与 configurable_default 源码定义:

class IOLoop(Configurable):
    @classmethod
    def configurable_base(cls):
        return IOLoop

    @classmethod
    def configurable_default(cls):
        # 从 select 库中选择合适 io 机制, Linux 优先选择 epoll. 
        if hasattr(select, "epoll"):
            # platform.py 定义了 EPollIOLoop 实现, 派生于 IOLoop 
            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

即 IOLoop 调用了基类 Configurable new 函数, 代码中又调用了
重写的 configurable_xxx 函数来实现找到 平台底层 io 库的目的.
实际上 EPollIOLoop 派生于 PollIOLoop, PollIOLoop 里封装了更
详细的 ioloop 行为, 例如 add_handler update_handler remove_handler
事件的注册与注销, 读写事件监听都封装在其中, 同时支持三个事件io库,
即跨平台支持(通过更具体的派生类 eg: EPollIOLoop )

此时 语句3 的start 函数其实是 EPollIOLoop 的 start() 调用.

总结:
1. tornado 单例为了保证全局(线程中唯一, 引入threading.Lock() 保证单例生成成功)
见 with IOLoop._instance_lock: 语句( _instance_lock = threading.Lock() )
2. tornado IOLoop 返回的单例 ioloop 实例引用并不是 IOLoop 类本身实例化,而是其派生出的类的实例化, 根据 Python select 基础库属性选择系统提供的 io 库.
eg: Linux epoll, BSD: kqueue, Windows: select
并且派生类围绕其底层接口做封装操作.
3. 为了实现 2描述的跨平台支持, IOLoop 实例在生成时, tornado 利用了 new方法干预了类的实例生成, 其代码实现很好地利用了 Python 语言本身提供的机制.
4. tornado ioloop 单例类为保证每一个线程中唯一, 且在任何 io 事件能正常执行, 因此框架代码随处可见 IOLoop.current() 调用, 源码框架其实还是有点瑕疵的, (^ ^) 嘻嘻……

你可能感兴趣的:(tornado)