初探nodejs事件循环机制event loop

nodejs的特点

nodejs 具有事件驱动和非阻塞I/O的特点。

事件驱动是指nodejs把每一个任务当成事件来处理。

非阻塞I/O是nodejs遇到I/O任务时,会从线程池调度单独的线程处理I/O操作,不会阻塞主线程。

事件循环原理

Node.js 在主线程里维护了一个事件队列,当接到请求后,就将该请求作为一个事件放入这个队列中,然后继续接收其他请求。

当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:

  如果是非 I/O 任务,就亲自处理,并通过回调函数返回到上层调用;

  如果是 I/O 任务,就从 线程池 中拿出一个线程来处理这个事件,并指定回调函数,然后继续循环队列中的其他事件。

当线程中的 I/O 任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。

流程图

每次循环的六个阶段

  timers阶段:这个阶段执行定时器队列中的回调,如 setTimeout() 和 setInterval()

  I/O callbacks: 这个阶段执行几乎所有的回调。但是不包括close事件,定时器和setImmediate()的回调。

  idle, prepare: 这个阶段仅在内部使用,可以不必理会。

  poll: 等待新的I/O事件,node在一些特殊情况下会阻塞在这里。

  check: setImmediate()的回调会在这个阶段执行。

  close callbacks: 例如socket.on('close', ...)这种close事件的回调。

下面我们来按照代码第一次进入libuv引擎后的顺序来详细解说这些阶段:

  当个v8引擎将js代码解析后传入libuv引擎后,循环首先进入poll阶段。

  poll阶段的执行逻辑如下:

    先查看poll queue中是否有事件,有事件就按先进先出的顺序依次执行回调。

    当queue为空时,会检查是否有setImmediate()的callback,如果有就进入check阶段执行这些callback。

    当queue为空时,同时也会检查是否有到期的timer,如果有,就把这些到期的timer的callback按照调用顺序放到timer queue中,之后循环会进入timer阶段执行queue中的 callback。

    这两者的顺序是不固定的,收到代码运行的环境的影响。

    如果两者的queue都是空的,那么loop会在poll阶段停留,直到有一个i/o事件返回,循环会进入i/o callback阶段并立即执行这个事件的callback。

    值得注意的是,poll阶段在执行poll queue中的回调时实际上不会无限的执行下去。

    有两种情况poll阶段会终止执行poll queue中的下一个回调:1.所有回调执行完毕。2.执行数超过了node的限制。

特别感谢:

https://www.cnblogs.com/onepixel/p/7143769.html

 

原文地址:https://www.cnblogs.com/zifayin/p/11419808.html