Tornado源碼閱讀(一) --- IOLoop之創(chuàng)建ioloop

本文的測試環(huán)境是在MacOS,因此使用的多路復(fù)用的網(wǎng)絡(luò)IO為kqueue而不是epoll,對應(yīng)的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() 實際上是創(chuàng)建一個IO循環(huán)的對象,這里是KQueueIOLoop,Linux的系統(tǒng)則是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,則調(diào)用IOLoop.instance()方法創(chuàng)建一個IOLoop的實例作為currnet返回。由于tornado的包裝,實際上IOLoop返回的并不是IOLoop的實例對象,而是KQueueIOLoop實例對象。

為什么IOLoop實例化的對象KQueueIOLoop呢?想知道答案就得揭開IOLoop.instance()神秘面紗,表面上看,該方法創(chuàng)建的IOLoop實例對象,并綁定到IOLoop._instance上。

@staticmethod
def instance():
    if not hasattr(IOLoop, "_instance"):
        with IOLoop._instance_lock:
            if not hasattr(IOLoop, "_instance"):
                # 新實例要經(jīng)過兩次check檢查
                IOLoop._instance = IOLoop()
    return IOLoop._instance

IOLoop繼承自Configurable基類,IOLoop 自身沒有常見的初始化"構(gòu)造函數(shù)"(init)。顯然需要再查看Configurable基類。不看不知道,一看tornado的作者還真會玩。Configurable是一個設(shè)計很精巧的類,通過不同子類的繼承來適配?;愒谧宇悇?chuàng)建的時候做一些適配的事情。相比init, new稱之為構(gòu)造函數(shù)更準確。

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

        if cls is base:
            # 通過調(diào)用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 對象為對應(yīng)的網(wǎng)絡(luò)io模式,對于unix系統(tǒng),因此這里是 kqueue方式,即位KQueueIOLoop類。
        instance = super(Configurable, cls).__new__(impl)

        # 通過initialize 方法,傳接 并返回,這個就是上面current對象,即
        instance.initialize(*args, **init_kwargs)
        return instance

IOLoop在創(chuàng)建的時候,通過基類new方法調(diào)用子類的configurable_base和configurable_default適配不同子類的特性。這里通過IOLoop的configurable_default方法選擇了unix系統(tǒng)的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

根據(jù)平臺確定了impl為kqueue之后,將會通過new創(chuàng)建實例對象,就是這一步,創(chuàng)建了KQueueIOLoop而不是IOLoop的對象。Configurable自身不定義initialize。這里就調(diào)用了KQueueIOLoop的initialize方法。

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

KQueueIOLoop的方法很簡單,其中實現(xiàn)了一個_KQueue,這個類用于操作unix系統(tǒng)上的kqueue的網(wǎng)絡(luò)io相關(guān)封裝,例如注冊事件,poll調(diào)用等。然后KQueueIOLoop帶用其父類(PollIOLoop)的initialize方法。有沒有發(fā)現(xiàn),調(diào)用的控制權(quán)一直在各個父類基類中跳轉(zhuǎn)。大概是 IOLoop -> Configurable -> IOLoop -> KQueueIOLoop -> PollOLoop -> IOLoop -> PolIOLoop。

class PollIOLoop(IOLoop):

    def initialize(self, impl, time_func=None, **kwargs):
        # 調(diào)用父類的IOLoop的initialize方法
        super(PollIOLoop, self).initialize(**kwargs)
        # _KQueue類
        self._impl = impl 

PollIOLoop繼承自IOLoop,PollIOLoop調(diào)用其父類的initialize方法。此時調(diào)用make_current為None,因此又會調(diào)用IOLoop.current()的方法,怎么又是IOLoop.current?我們不就是從客戶端邏輯(相對于庫)調(diào)用這個方法進來的么?注意,不同于第一次客戶端調(diào)用的時候,當時intances是True。也就是此時直接返回IOLoop._current.instance,前面正是因為current為None,才需要通過IOLoop的創(chuàng)建對象。當然此時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正好填補這個空白,創(chuàng)建一個綁定就好嘛。

def make_current(self):
    """Makes this the `IOLoop` for the current thread.
    將IOLoop實例對象綁定到local線程_current的instance屬性。

    """
    IOLoop._current.instance = self

的確,make_current把當前的類實例(KQueueIOLoop)創(chuàng)建并綁定。通過前面巧妙的設(shè)計,根據(jù)平臺選擇了網(wǎng)絡(luò)io的模式。接下來還得根據(jù)io模式綁定IO監(jiān)聽事件。繼續(xù)閱讀PollIOLoop,可以發(fā)現(xiàn)通過add_handler方法喝Waker實現(xiàn)。


class PollIOLoop(IOLoop):

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

        ...

        # posix風格的文件讀取操作,網(wǎng)絡(luò)io本質(zhì)也是文件操作
        self._waker = Waker()
        # 添加事件綁定,前面條用子類KQueueIOLoop的時候,傳了_KQueue類
        self.add_handler(self._waker.fileno(),
                         lambda fd, events: self._waker.consume(),
                         self.READ)

add_handler方法處理文件描述符,其中stack_context類通過wrap包裝一個上下文類似的東西。具體數(shù)據(jù)結(jié)構(gòu)沒有仔細看,留待日后研究,總而言之,這個方法借助之前的_KQueue類注冊網(wǎng)絡(luò)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對象成功的創(chuàng)建。創(chuàng)建ioloop對象之后,server還不回啟動,需要調(diào)用start啟動。在啟動之前,也需要通過add_hanndler綁定事件函數(shù)。至于start的工作原理,下回再研究。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,535評論 19 139
  • (代碼縮進有點問題 大家可以看源碼) 我們來分析一下tornado.ioloop.IOLoop.instance(...
    lpj24閱讀 5,979評論 0 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,638評論 18 399
  • 本文將通過最簡單的Hello world代碼分析Tornado本身的結(jié)構(gòu),在此基礎(chǔ)上再簡單介紹下Tornado適用...
    靡不有初LB閱讀 3,074評論 0 7
  • 2014年,我背著我的行李箱來到了陌生的城市,金華。那一年我非常的難以度過,沒有朋友,只有自己一個人,想出去走走都...
    直帥閱讀 440評論 0 1

友情鏈接更多精彩內(nèi)容