Tornado底层学习 (1) — tornado ioloop start 的过程

写在分享前:
基于 tornado 开发 http 服务已经 3个月了, 通过 F2E.im 项目模仿, 也开始重新
重写整个业务代码(现成的开源代码并不适用复杂的使用场景, 数据库和缓存的
模块就需要使用新的模块组件), 在这个过渡阶段中, 也遇到诸如
tornado-restful 封装的库在 tornado.gen.coroutine 搭配并不成功的坑, 因此
有必要在使用第三方开源库时, 理解 tornado 底层的代码, 以便修改第三方源码
或者做代码结构调整, 合理开发. 在 Write Less, Do More 之前, 学会
Read and Appreciate Other’s work.

每一个 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

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

先展示 ioloop.py 里 class 关系图:
  tornado-ioloop
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源码:

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

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

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

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

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

IOLoop configurable_base 与 configurable_default 源码定义:

即 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() 调用, 源码框架其实还是有点
瑕疵的, (^ ^) 嘻嘻……

原创文章,转载请注明: 转载自kaka_ace's blog

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