Tornado-6.0.3源碼分析之IO Event 事件的IOLoop類及其子類

一、前述

通過上一篇的Configurable類的相關介紹,這裏再進一步說明IOLoop類,其類的註釋也說明了,在Tornado 6.0版本中,Tornado的IOLoop類只是對asyncio event loop的包裝而已。也就是說本質上就是asyncio庫。爲了方便查看IOLoop類相應的類關係。這裏也放置一張類圖。
在這裏插入圖片描述
從圖中可以看到,IOLoop是直接繼承至Configurable類的。在IOLoop中,主要完成的基本任務如下:

  • 重載實現Configurable的接口,定義基本的基類和默認的實現類。
  • 定義功能接口。該接口主要是提供給上層調用,同時也規定其子類必須要實現的功能接口。

與此同時,需要了解的是,對於一個套接字,當對其進行狀態監聽時,該套接字可能觸發的狀態有:可讀、可寫、出錯;對於網絡模型中套接字的監聽,一般是通過註冊一個回調處理函數,在相應的狀態變化時,通過調用對應的回調函數進行處理。asyncio對於特定的網絡套接字的監聽也是通過這樣的手段。asyncio運行所有的異步實現都是在event loop上,一般來說,對於主線程,在沒有當前沒有event loop實例時,會調用默認的事件策略去生成一個,並設置爲當前event loop;但是對於非主線程,並不會主動生成一個事件循環實例,而是需要用戶自己實例化一個。考慮到這個情況,Tornado在實現上,實現了兩個異步IO循環實例類,即AsycIOMainLoop和AsyncIOLoop。

爲了方便說明,約定IOLoop指的是Tornado的IOLoop類或者子類,而ioloop指的是asyncio庫的event loop。

二、IOLoop類

  • 重載實現Configurable的接口
    (1)configure方法,Tornado對該方法只是添加了類型檢查判斷而已。
    (2)configurable_base,定義返回的直接基類是IOLoop類
    (3)configurable_default,定義默認實例化的子類的是AsyncIOLoop類,這個子類在每次實例化時,都會生成一個新的asyncio的event loop實例,用戶可通過實例化時,指定make_current參數,來設置要不要使用新的event loop作爲當前的事件循環實例。(這點下面再細說怎麼用)
    (4)initialize,實現初始化方法,可指定make_current參數,設置當前的Event 事件循環。

  • 定義功能接口
    這裏定義了一系列接口,基本上與asyncio庫的高層用戶接口差不多,只是Tornado作了更多的邏輯優化處理而已;除此之外,還有一些靜態方法方便用戶調用。
    方便用戶調用的方法,如instance、clear_instance、current、clear_current等。
    比如看下current方法

@staticmethod
def current(instance: bool = True) -> Optional["IOLoop"]:  # noqa: F811
    """Returns the current thread's `IOLoop`.
       返回當前線程的IOLoop實例
    """
    # 嘗試獲取asyncio的ioloop實例
    loop = asyncio.get_event_loop()
    except (RuntimeError, AssertionError):
        if not instance:
            return None
        raise
    try:
    	# 嘗試獲取與當前asyncio的ioloop相對應的Tornado的IOLoop類實例
    	# 這裏的_ioloop_for_asyncio是一個類變量,屬於字典類型,鍵是asyncio的ioloop實例,值是Tornado的IOLoop實例
        return IOLoop._ioloop_for_asyncio[loop]
    except KeyError:
    	# 走到這裏表示,當前有asyncio的ioloop,但是還沒有相對應的IOLoop實例,因此,若在調用current時,指定了instance,指示在沒有IOLoop時,就創建一個,則這裏就實例化創建一個實現類AsyncMainIOLoop。
        if instance:
            from tornado.platform.asyncio import AsyncIOMainLoop
            current = AsyncIOMainLoop(make_current=True)  # type: Optional[IOLoop]
        else:
            current = None
    return current

其它的功能接口定義,如add_handler、update_handler、remove_handler、start、stop等,則由其直接子類BaseAsyncIOLoop來實現。

三、BaseAsyncIOLoop類

首先這個類重載實現了自己的初始化過程,即initialize方法,在該方法上,可以看到其有一些相應的實例變量。這裏簡單介紹如下:

    def initialize(  # type: ignore
        self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any
    ) -> None:
    	# 這裏記錄下,與當前類相對的asyncio的ioloop實例。這樣在後面需要用到asyncio的ioloop實例時,
    	# 不必每次都去_ioloop_for_asyncio這個類字典變量中查閱,可以提高程序的執行速度,也算是一種優化吧。
        self.asyncio_loop = asyncio_loop
        # Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler)
        # type: Dict[int, Tuple[Union[int, _Selectable], Callable]]
        # 這裏的英文註釋就很清晰了,這是一個維護相應的fd狀態變化時的回調處理函數的一個映射表
        self.handlers = {}  
        # Set of fds listening for reads/writes
        # 下面這兩個集合是分別對可讀或者可寫狀態進行監聽的fd的集合
        # 
        self.readers = set()  
        self.writers = set() 
        self.closing = False
		# 這個是一個健全性判斷處理。因爲Tornado的IOLoop是對asyncio的ioloop的包裝實現。也就是說當調用asyncio的close相關的方法時,也能正常停止異步相關的功能,但是對於Tornado來說,並不知道asyncio已經被停止,從而導致_ioloop_for_asyncio中存在未正確釋放的映射鍵值對。因此這裏在實現化時,添加一個檢查,去掉已關閉的ioloop映射。
        for loop in list(IOLoop._ioloop_for_asyncio):
            if loop.is_closed():
                del IOLoop._ioloop_for_asyncio[loop]
        IOLoop._ioloop_for_asyncio[asyncio_loop] = self
        self._thread_identity = 0
    
        # 往上初始化,即初始化IOLoop類的initialize方法
        super(BaseAsyncIOLoop, self).initialize(**kwargs)

        def assign_thread_identity() -> None:
            self._thread_identity = get_ident()
		# 在異步操作啓動後,獲取記錄下本線程的標識值,以便於在需要判斷線程安全的地方,調用線程安全的接口實現,如add_callback方法
        self.add_callback(assign_thread_identity)

實現IOLoop定義的接口。

  • add_handler方法,添加相應的fd監聽類型和註冊對應的回調處理函數
  • update_handler方法,更新特定fd的監聽類型
  • remove_handler方法,去年特定fd的監聽
  • start方法,啓動運行asyncio的ioloop事件循環,即關鍵點就是self.asyncio_loop.run_forever()
  • stop方法,即停止asyncio的io事件循環
  • close方法,關閉所有的asyncio的io事件循環。
  • add_callback方法,這裏Tornado會根據調用者與運行線程的關係,安全的判斷,並調用線程安全的回調函數。
  • 其它方法,這裏就不一一介紹了。

四、實現類AsyncIOMainLoop和AsyncIOLoop

調用實例化AsyncIOMainLoop時,一般代表着正在創建一個IOLoop類,使其對應於asyncio的event loop事件,換句話說,即當前是一定有asyncio的event loop時,纔會嘗試去實例化這個類。
而AsyncIOLoop類的區別點是,其在每次的初始化initialize方法中,都會創建一個asyncio類的event loop實例,然後使得AsyncIOLoop的實例與這個新建的event loop相關聯,但是這裏要注意,當這樣使用時,要配合添加make_current參數爲True,這樣就能使這個新的event loop得到正常使用。
對於AsyncIOLoop類來說,初始化過程主要是涉及其重載實現的initialize方法和make_current方法。
在其initialize中調用到了loop = asyncio.new_event_loop(),而在make_current方法中調用到了asyncio.set_event_loop(self.asyncio_loop)。從而使得新創建的asyncio可以正常使用。

五、使用方法

  • 對於在主線程中運行Tornado時
    import tornado.ioloop
    # 調用類方法current,這裏會去查找判斷asyncio的ioloop所對應的IOLoop實例,
    # 若沒有則實例化一個AsyncIOMainLoop與之對應。當然也可以調用intance方法。
    tornado.ioloop.IOLoop.current().start()
  • 對於在子線程中運行Tornado時
	import tornado.ioloop
	import asyncio
    # 方法一:因爲IOLoop默認使用AsyncIOLoop類,因此可以使用make_current參數,
    #         確保新建的asyncio的event loop類可以使用。
    tornado.ioloop.IOLoop(make_current=True).start()
    # 方法二:先手動創建並設置當前的asyncio類的ioloop實例,然後調用current方法,
    #        來實例出一個AsyncIOMainLoop類實例與這個手動創建的asyncio的ioloop相關聯
    io_loop = asyncio.new_event_loop()
    asyncio.set_event_loop(io_loop)
    tornado.ioloop.IOLoop.current().start()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章