Tornado源码阅读(一) --- IOLoop之创建ioloop

本文的测试环境是在MacOS,因此使用的多路复用的网络IO为kqueue而不是epoll,对应的IOLoop实例对象也是KQueueIOLoop。

在介绍Epoll模式的笔记中,最后写了一个tornado的使用epoll的例子。这个例子是如何工作的呢?下面来读一读tornado的源码。

启动一个tornado server很简单,只需要下面几code:

io_loop = tornado.ioloop.IOLoop.current()
callback = functools.partial(connection_ready, sock)
io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
io_loop.start()

tornado.ioloop.IOLoop.current() 实际上是创建一个IO循环的对象,这里是KQueueIOLoop,Linux的系统则是EPollIOLoop。

下面是current的源码,该方法目的就是从local线程中获取KQueueIOLoop(如果存在的话,否则则新建一个)


@staticmethod
def current(instance=True):
    current = getattr(IOLoop._current, "instance", None)
    if current is None and instance:
        return IOLoop.instance()
    return current

程序首先判断 IOLoop._current对象(_current对象是一个线程local)的instance属性,如果没有current,则调用IOLoop.instance()方法创建一个IOLoop的实例作为currnet返回。由于tornado的包装,实际上IOLoop返回的并不是IOLoop的实例对象,而是KQueueIOLoop实例对象。

为什么IOLoop实例化的对象KQueueIOLoop呢?想知道答案就得揭开IOLoop.instance()神秘面纱,表面上看,该方法创建的IOLoop实例对象,并绑定到IOLoop._instance上。

@staticmethod
def instance():
    if not hasattr(IOLoop, "_instance"):
        with IOLoop._instance_lock:
            if not hasattr(IOLoop, "_instance"):
                # 新实例要经过两次check检查
                IOLoop._instance = IOLoop()
    return IOLoop._instance

IOLoop继承自Configurable基类,IOLoop 自身没有常见的初始化"构造函数"(init)。显然需要再查看Configurable基类。不看不知道,一看tornado的作者还真会玩。Configurable是一个设计很精巧的类,通过不同子类的继承来适配。基类在子类创建的时候做一些适配的事情。相比init, new称之为构造函数更准确。

class Configurable(object):
    def __new__(cls, *args, **kwargs):
        base = cls.configurable_base()
        init_kwargs = {}

        if cls is base:
            # 通过调用configured_class方法,可以绑定 base.__impl_class 为epoll还是kqueue。
            impl = cls.configured_class()
            if base.__impl_kwargs:
                init_kwargs.update(base.__impl_kwargs)
        else:
            impl = cls

        init_kwargs.update(kwargs)

        # impl 对象为对应的网络io模式,对于unix系统,因此这里是 kqueue方式,即位KQueueIOLoop类。
        instance = super(Configurable, cls).__new__(impl)

        # 通过initialize 方法,传接 并返回,这个就是上面current对象,即
        instance.initialize(*args, **init_kwargs)
        return instance

IOLoop在创建的时候,通过基类new方法调用子类的configurable_base和configurable_default适配不同子类的特性。这里通过IOLoop的configurable_default方法选择了unix系统的kqueue方式。

@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

根据平台确定了impl为kqueue之后,将会通过new创建实例对象,就是这一步,创建了KQueueIOLoop而不是IOLoop的对象。Configurable自身不定义initialize。这里就调用了KQueueIOLoop的initialize方法。

class KQueueIOLoop(PollIOLoop):
    def initialize(self, **kwargs):
        super(KQueueIOLoop, self).initialize(impl=_KQueue(), **kwargs)

KQueueIOLoop的方法很简单,其中实现了一个_KQueue,这个类用于操作unix系统上的kqueue的网络io相关封装,例如注册事件,poll调用等。然后KQueueIOLoop带用其父类(PollIOLoop)的initialize方法。有没有发现,调用的控制权一直在各个父类基类中跳转。大概是 IOLoop -> Configurable -> IOLoop -> KQueueIOLoop -> PollOLoop -> IOLoop -> PolIOLoop。

class PollIOLoop(IOLoop):

    def initialize(self, impl, time_func=None, **kwargs):
        # 调用父类的IOLoop的initialize方法
        super(PollIOLoop, self).initialize(**kwargs)
        # _KQueue类
        self._impl = impl 

PollIOLoop继承自IOLoop,PollIOLoop调用其父类的initialize方法。此时调用make_current为None,因此又会调用IOLoop.current()的方法,怎么又是IOLoop.current?我们不就是从客户端逻辑(相对于库)调用这个方法进来的么?注意,不同于第一次客户端调用的时候,当时intances是True。也就是此时直接返回IOLoop._current.instance,前面正是因为current为None,才需要通过IOLoop的创建对象。当然此时current为None,即直接返回None。接下来自然运行make_current方法。

def initialize(self, make_current=None):
    if make_current is None:
        if IOLoop.current(instance=False) is None:
            self.make_current()
    elif make_current:
        if IOLoop.current(instance=False) is None:
            raise RuntimeError("current IOLoop already exists")
        self.make_current()

make_current方法干点啥好呢?当然你肯定想到了,既然我们之前IOLoop.current方法是为了获取IOLoop._current.instance,并且一直为None,那么make_current正好填补这个空白,创建一个绑定就好嘛。

def make_current(self):
    """Makes this the `IOLoop` for the current thread.
    将IOLoop实例对象绑定到local线程_current的instance属性。

    """
    IOLoop._current.instance = self

的确,make_current把当前的类实例(KQueueIOLoop)创建并绑定。通过前面巧妙的设计,根据平台选择了网络io的模式。接下来还得根据io模式绑定IO监听事件。继续阅读PollIOLoop,可以发现通过add_handler方法喝Waker实现。


class PollIOLoop(IOLoop):

    def initialize(self, impl, time_func=None, **kwargs):

        ...

        # posix风格的文件读取操作,网络io本质也是文件操作
        self._waker = Waker()
        # 添加事件绑定,前面条用子类KQueueIOLoop的时候,传了_KQueue类
        self.add_handler(self._waker.fileno(),
                         lambda fd, events: self._waker.consume(),
                         self.READ)

add_handler方法处理文件描述符,其中stack_context类通过wrap包装一个上下文类似的东西。具体数据结构没有仔细看,留待日后研究,总而言之,这个方法借助之前的_KQueue类注册网络io事件。

def add_handler(self, fd, handler, events):
    fd, obj = self.split_fd(fd)
    self._handlers[fd] = (obj, stack_context.wrap(handler))
    self._impl.register(fd, events | self.ERROR)

此时,ioloop对象成功的创建。创建ioloop对象之后,server还不回启动,需要调用start启动。在启动之前,也需要通过add_hanndler绑定事件函数。至于start的工作原理,下回再研究。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容