tornado版本: 4.4.1linux
tornado在linux下使用epoll解決併發,解決c10k問題。web
關於epoll瞭解能夠自行搜索瞭解。併發
tornado的epoll實現主要在tornado.ioloop裏面。app
咱們經過tornado的啓動流程學習和分析。socket
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方法:tornado
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:oop
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()code
@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的使用詳細的閱讀源碼便可。