解决了我一个疑问的一篇文章
转载自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 关系图:
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() 调用, 源码框架其实还是有点瑕疵的, (^ ^) 嘻嘻……