08 | Tornado源码分析:IOLoop 之 事件循环

2020-07-01 15:40:59 浏览数 (1)

各位朋友: 大家端午节安康,本期我们来聊聊 IOLoop 中最重要的一个方法----start(),为何这个方法如此之重要呢?写过 Tornado程序的朋友对这句代码一定不陌生:IOLoop.current.start().没错最后那个点方法就是start().本期我们就来聊聊这个方法。 在开始之前,我们先来聊聊一个基础概念就是:事件循环。什么是事件循环(轮训)呢?从字面意思看有重复询问事件的意思,我们通过一个图来了解一下:

此图简单的描述了epoll 模型下的事件轮训 API 的调度过程,输入的是 文件描述符,输出的是可读可写的事件。同时提供一个timeout参数,当没有事件到来的时候就最多等待timeout秒,线程一直处于阻塞的状态,一旦期间有任何事件的到来都会立刻返回。时间过了之后仍然没有事件到来也会返回。拿到事件之后,线程就可以依次处理相应的事件,处理完成了就继续轮序。这个过程是一个死循环,这个死循环就是事件循环(轮训)。 有了这个基础知识后,我们看一下源码:

初步看代码量还有有些多的,我们主要是分析其运行原理我做了一个简化版本的代码大家可以看一下(出去了不是主逻辑的一些代码):

代码语言:javascript复制

# -*- encoding: utf-8 -*-
# !/usr/bin/python
"""
@File    : start_function_core.py
@Time    : 2020/6/26 18:23
@Author  : haishiniu
@Software: PyCharm
"""
# 请忽略这些导入模块 只是为了不出现个别变量未定义的警告
from concurrent.futures import thread
from tornado import ioloop
IOLoop = None

def start(self):
    # 如果已经启动,则抛出异常
    if self._running:
        raise RuntimeError("IOLoop is already running")
    # 如果已经停止,将停止标记设置为False,并立即返回
    if self._stopped:
        self._stopped = False
        return
    old_current = getattr(IOLoop._current, "instance", None)  # 自己本身的实例
    IOLoop._current.instance = self  # 设置成当前线程的IOloop,进行工作。

    # 启动IOLoop实例的线程的标识符
    self._thread_ident = thread.get_ident()

    # 将IOLoop实例设置为运行状态
    self._running = True
    try:
        # 事件循环
        while True:
            # 逐个执行每一个添加到IOLoop的回调函数
            ncallbacks = len(self._callbacks)
            for i in range(ncallbacks):
                self._run_callback(self._callbacks.popleft())
            # 逐个执行每个到期的定时器的回调函数
            due_timeouts = []
            for timeout in due_timeouts:
                if timeout.callback is not None:
                    self._run_callback(timeout.callback)

            # 若有回调函数,那么事件轮询的 超时时间设置为 0,
            # 以防,当没有任何事件发生时,事件轮询一直在等待,进而导致回调函数无法即时执行
            if self._callbacks:
                poll_timeout = 0.0
            # 如果有定时器,那么将超时时间设置为默认超时间 和 最先被调度的定时器的到期时间 中的较小值,
            # 以防,定时器无法按时执行
            elif self._timeouts:
                poll_timeout = self._timeouts[0].deadline - self.time()
                # poll_timeout = max(0, min(poll_timeout, _POLL_TIMEOUT))
            # 当没有回调函数,也没有定时器的时候,将事件轮询超时时间设置为默认值
            else:
                poll_timeout = None
                # 事件循环的退出条件:运行标记为False
            if not self._running:
                break
            # 事件轮询
            try:
                # 此处会阻塞 poll_timeout ,不知大家现在能否理解 设置的3600这个数了?
                event_pairs = self._impl.poll(poll_timeout)
            except Exception as e:
                pass
            # 逐个执行发生了事件的文件描述符的处理函数
            self._events.update(event_pairs)

            while self._events:
                fd, events = self._events.popitem()
                try:
                    fd_obj, handler_func = self._handlers[fd]
                    handler_func(fd_obj, events)
                except (OSError, IOError) as e:
                    pass
    # 事件循环退出之后
    finally:
        # 重置停止标记为False
        self._stopped = False
        # 清除闹钟
        # 恢复当前线程的IOLoop实例
        # 恢复wakeup文件描述符

好了,本期的分享就先到这里,还有不懂的地方可以给我后台留言,收到会在时间允许的情况下回复大家。这期我们已经介绍完了Tornado最核心的事件轮训的逻辑,相信大家也有了一个全新的认知,后续我们会接着分享与之相关的内容,比如上下文管理,神秘的Future 对象等等,尽情期待!

0 人点赞