Tornado源码分析手记 —— IOLoop核心实现

最近开始阅读开源项目的源码了,鉴于一直用Tornado做项目,就从它着手开始吧。

今天分析的是Tornado的"Killer Technique",哈哈,其实没那么夸张了。我们知道Tornado采用了与Node.js相同的单线程事件驱动模型,那么它就需要一个事件轮询机制,我没有看过Node.js的源码,所以不太清楚它的机制。Tornado在IO层面主要使用了两种解决方案:

  • select
  • epoll
    通过Configurable类实现IOLoop的工厂模式,在*NIX系统下默认采用了epoll的方式。

接下来我们就逐行进行分析,IOLoop类的代码不会都看,阅读的顺序是从start函数开始。

if self._running:
  raise RuntimeError("IOLoop is already running")
self._setup_logging()
if self._stopped:
  self._stopped = False
  return
old_current = getattr(IOLoop._current, "instance", None)
IOLoop._current.instance = self
self._thread_ident = thread.get_ident()
self._running = True

这部分其实就是进行了一些状态检测,然后设置日志,设置标志位的值。不多啰嗦了。
主要来看下面这个大的循环体:

with self._callback_lock:
  callbacks = self._callbacks
  self._callbacks = []

首先在线程同步锁下将所有的callback获取出来,然后清空原来的callback数组。

due_timeouts = []
if self._timeouts:
  now = self.time()
  while self._timeouts:
    if self._timeouts[0].callback is None:
      heapq.heappop(self._timeouts)
      self._cancellations -= 1
    elif self._timeouts[0].deadline <= now:
      due_timeouts.append(heapq.heappop(self._timeouts))
    else:
      break
if (self._cancellations > 512 and self._cancellations > (len(self._timeouts) >> 1)):
  self._cancellations = 0
  self._timeouts = [x for x in self._timeouts if x.callback is not None]
  heapq.heapify(self._timeouts)

这部分比较长,主要是处理了延时事件,支持异步的gen.sleep的实现就与这部分有关。

首先声明空数组due_timeouts来存放被trigger的事件回调,这个timeouts队列比较有意思,它用到了heapq这个包来保证数组内的元素存放顺序是一个堆的结构,这样一来每次取出来的元素都是最小的。然后只需要判断取出的最小元素是不是过时了,如果这个事件被取消了,那么直接pop掉进入下次循环;如果这个事件过时了,就直接加入待执行的due_timeouts中,进入下次循环;如果没有过时,由于这是最小元素,所以它后面的元素肯定也没有过时所以干脆直接跳出循环,接着进行下面的内容。

接下来是一个优化操作,如果取消的事件多于512个并且大于总数的一半时,就把timeouts进行清理,清理结束后再进行堆排序。

下面一部分代码就不放了,主要是执行刚才上面准备好的callbacks和timeouts。

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 = _POLL_TIMEOUT

这里又进行了一次callbacks的检查,如果有需要执行的回调,那么就让poll等待的时间为0,如果有timeouts,就让poll等待的时间为还有多久触发timeout事件的时间,同时这个时间不能超过预设的最长时间。

接下来进行了一次状态检测,如果IOLoop已经停止,那么跳出循环。

try:
  event_pairs = self._impl.poll(poll_timeout)

开始等待IO事件了。

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:
    if errno_from_exception(e) == errno.EPIPE:
      pass
    else:
      self.handle_callback_exception(self._handlers.get(fd))
   except Exception:
      self.handle_callback_exception(self._handlers.get(fd))
fd_obj = handler_func = None

这里得到IO事件的触发者,然后得到它的处理函数,并且执行这个函数,然后进行异常处理。

这就是IOLoop的大致思路,通过IOLoop,我们就可让一个工作变成一组可序列化并且粒度足够小的事件,依次执行。通过select/epoll机制来实现同时对多个socket进行同时处理,避免轮询浪费CPU时间,是效率高的关键因素。

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

推荐阅读更多精彩内容

  • 抽空看了下tornado的源码,会将几个关键部分以专栏文章的形式记录下来,算是对学习的一个汇总,也希望能对使用to...
    throwsterY阅读 723评论 2 3
  • 本文将通过最简单的Hello world代码分析Tornado本身的结构,在此基础上再简单介绍下Tornado适用...
    靡不有初LB阅读 2,972评论 0 7
  • epoll概述 epoll是linux中IO多路复用的一种机制,I/O多路复用就是通过一种机制,一个进程可以监视多...
    发仔很忙阅读 10,881评论 4 35
  • Tornado 所谓的异步:就是你调用我之后,我发现数据没准备好,那我就不处理,而是跳到程序的其他地方继续执行,等...
    OMSobliga阅读 6,102评论 3 11
  • 名称 libev - 一个 C 编写的功能全面的高性能事件循环。 概要 示例程序 关于 libev Libev 是...
    hanpfei阅读 15,242评论 0 5