您的位置:首页 > 运维架构

[转]tornado ioloop start 的过程

2015-08-13 20:23 531 查看
解决了我一个疑问的一篇文章

转载自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 <xiang.ace@gmail.com>
# @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() 调用, 源码框架其实还是有点瑕疵的, (^ ^) 嘻嘻……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tornado