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

tornado源码学习之ioloop

2017-03-08 00:00 344 查看
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的使用详细的阅读源码即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: