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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章