程序员今日看点代码改变世界

Node探秘之事件循环(1)--几个基本要素

2016-11-06  本文已影响2381人  游泳的石头

前言

对于想要真正理解JavaScript工作机制的人,理解JS的事件循环(Event Loop)应当是首当其冲的绕不过的坎。因为事件驱动模型的设计和事件循环机制的实现,才使得js成为一门真正纯异步,使得回调函数十分普遍的编程语言,也是Node之所以被作为网络应用节点,用于高效处理并发I/O的原因。

当你真正理解了事件循环机制时,下面这样的一些问题相信你也会就了然了。

问题1

解释JavaScript事件模型本身的文章还是非常多的,并且不乏有不少出色的文章。本文将引用部分文章的图片,结合对Node源码的分析,逐步理出Node事件循环机制的实现原理。

因为在写本文时,主要参考的是朴灵老师写的《深入浅出nodejs》,写这本书时的node稳定版本为0.10.13,且早期版本的node源码较少,读起来较为容易理解,因此这里的代码分析主要是基于0.10.13来进行的。

事件循环

大家都知道,一般我们会讲JS是单线程的,Node在处理I/O的时候也是单线程的,好处就是没有线程的切换和数据共享的问题。但实际上,在系统底层,非阻塞的I/O解决方案,只有Network I/O部分,linux底层使用epoll,windows下使用IOCP。文件等操作则没有很好的系统解决方案,因此为了模拟实现非阻塞的I/O方案,Node在libuv层启用了一个线程池,用于调用系统的阻塞式I/O。

因此,实际上,node只是在应用层属于单线程,底层其实通过libuv维护了一个阻塞I/O调用的线程池。

JS之所以可以在执行异步操作时可以设置好callback函数之后,就继续执行后续代码,当异步操作执行完成时,又能够及时触发回调函数,获取异步结果。最关键的核心就在于事件循环。

在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程被称为tick,中文翻译应该意为“滴答”,就像时钟一样,每滴答一下,就表示过去了1s。这个tick也有点这个意思,每循环一次,都表示本次tick结束,下次tick开始。每个tick开始之初,都会检查是否有事件需要处理,如果有,就取出事件及关联的callbak函数,如果存在有关联的callback函数,就把事件的结果作为参数调用这个callback函数执行。如果不在有事件处理,就退出进程。

Tick流程图

那么在每个tick的过程中,如何判断是否有事件需要处理,先要引入一个概念,叫做“观察者”(watcher)。每一个事件循环都有一个或者多个观察者,判断是否有事件要处理的过程就是向这些观察者询问是否有需要处理的事件

Node的观察者有这样几种:

idle观察者:顾名思义,就是早已等在那里的观察者,以后会说到的process.nextTick就属于这类

I/O观察者:顾名思义,就是I/O相关观察者,也就是I/O的回调事件,如网络,文件,数据库I/O等

check观察者:顾名思义,就是需要检查的观察者,后面会说到的setTimeout/setInterval就属于这类

事件循环是一个典型的生产者/消费者模型。异步I/O,网络请求,setTimeout等都是典型的事件生产者,源源不断的为Node提供不同类型的事件,这些事件被传到对应的观察者那里,事件循环在每次tick时则从观察者那里取出事件并处理。

我们现在知道,JavaScript的异步I/O调用过程中,回调函数并不由我们开发者调用,事实上,在JavaScript发起调用到内核执行完I/O操作的过程中,存在一种中间产物,它叫做请求对象。这个请求对象会重新封装回调函数及参数,并做一些其他的处理。这个请求对象,会在异步事件完成时被调用,取出回调函数和参数,并传入执行结果进行回调。这个请求对象,在下一篇介绍setTimeout等的文章中会举例。

组装好请求对象,送入I/O线程池等待执行,实际上只是完成了异步I/O的第一步;第二步则是异步I/O被线程池处理结束后的回调,也就是执行回调

因为事件循环的逻辑就是这样,这里就不手工再重新绘制一张异步I/O流程图,直接把朴老师书里面的图贴上来给大家看。

应该说,事件循环、观察者、请求对象、I/O线程池,这四者共同组成了Node异步I/O模型的基本要素。

源码分析

下面我给出0.10.13中关于事件循环的源码分析结果图对上述分析给予说明

总结


根据源码分析图,可以请求出得出这样几个结论:

1、每次tick都会检查所有完成的回调事件,并一一执行回调函数。

2、跑完当前执行环境下能跑完的代码。每一个事件消息都被运行直到完成为止,在此之前,任何其他事件都不会被处理。这和C等一些语言不通,它们可能在一个线程里面,函数跑着跑着突然停下来,然后其他线程又跑起来了。JS这种机制的一个典型的坏处,就是当某个事件处理耗时过长时,后面的事件处理都会被延后,直到这个事件处理结束,在浏览器环境中运行时,可能会出现某个脚本运行时间过长,页面无响应的提示。Node环境则可能出现大量用户请求被挂起,不能及时响应的情况。

3、不同类型的观察者,处理的优先级不同,idle观察者最先,I/O观察者其次,check观察者最后。

上一篇下一篇

猜你喜欢

热点阅读