Node,js 事件循环原理(Event loop)

《新时期的Node.js入门》读书笔记

事件循环就是一个程序启动期间运行的死循环,Node代码虽然运行在单线程中,但仍能支持高并发,就是依靠事件循环实现的。

用户在前台不断产生事件,背后的循环(由浏览器实现)会逐个地处理它们。而JavaScript是单线程的,为了避免一个过于耗时的操作阻塞了其他操作的执行,就需要通过异步加回调的方式解决问题。

Node作为另一种运行时,事件循环由底层的 libuv 实现。


timers
I/O callbacks
idle prepare
poll
check
incoming: connections, data, etc.
close callbacks

上面的图例中,将事件循环分成了6个不同的阶段,其中每个阶段都维护着一个回调函数的队列,在不同的阶段,事件循环会处理不同的事件:

  • Timers:用来处理 setTimeOut() 和 setInterval() 的回调
  • I/O callbacks:大多数的回调方法在这个阶段执行,除了 timers、close 和 setImmediate 事件的回调
  • idle, prepare:仅仅在内部使用,我们不管它
  • Poll:轮询,不断检查有没有新的 IO 事件,事件循环可能会在这里阻塞
  • Check:处理 setImmediate 事件的回调。
  • close callbacks:处理一些close相关的事件,例如socket.on(‘close’, …)

假设时间循环进入到了某个阶段,即使这期间有其他队列中的事件就绪,也会先将当前阶段队列里的全部回调方法执行完毕后,再进入到下一个阶段

  • 1 timers
    这个阶段主要用来处理定时器相关的回调,当一个定时器超时后,一个事件就会加入到队列中,事件循环会跳转至这个阶段执行相应的回调函数
  • 2 IO callbacks
    该阶段主要是用来执行pending callback,例如一个TCP socket执行出现了错误,在一些 *nix系统下可能希望稍后再处理这类的错误,那么这个回调就会放在IO callback阶段来执行。
  • 3 poll
    这个阶段的主要任务是等待新事件的出现(该阶段使用epoll来获取新的事件),如果没有,事件循环可能会在此阻塞
    Poll阶段主要有两个步骤如下:
    (1)如果有到期的定时器,那么就执行定时器的回调方法
    (2)处理poll阶段对应的事件队列里面的事件
    当事件循环到达poll阶段时,如果这时没有要处理的定时器的回调方法,则会进行下面的判断:
    (1)如果poll队列不为空,则事件循环会按照顺序遍历执行队列中的回调函数,整个过程是同步的
    (2)如果poll队列为空, 会接着进行如下判断
    • 如果当前代码定义了setImmediate方法,事件循环会离开poll阶段,然后进入check阶段区执行setImmediate方法定义的回调方法
    • 如果当前代码没有定义setImmediate方法,那么事件循环会进入等待状态,并等待新的事件出现,这也是该阶段为什么会被命名为poll(轮询)的原因。此外,还会不断检查是否有相关的定时器超时,如果有则会跳转到timers阶段,然后执行相应的回调。
  • 4 check
    setImmediate是一个特殊的定时器方法,它占据了事件循环的一个阶段,整个check阶段就是为setImmediate方法而设置的。
    当事件循环到达poll阶段后,就会检查当前代码是否调用了setImmediate,但如果一个回调函数是被setImmediate方法调用的,事件循环就会跳出poll阶段进入check阶段。
  • 5 close
    如果一个socket或者一个句柄被关闭,那么就会产生一个close事件,该事件回被加入到对应的队列中。close阶段执行完毕后,本轮事件循环结束,循环进入到下一轮。
    在Node中,事件队列不止一个,定时器相关的事件和磁盘IO产生的事件需要不同的处理方式,如果把所有的事件都放在一个队列里,势必要增加许多类似switch/case的代码;那样的话倒不如将不同类型的事件归类到不同的事件队列里,然后一层层地遍历下来,如果当中出现了新的事件,就进行相应的处理。

你可能感兴趣的:(————,Node.js)