Node.JS 的特点
https://nodejs.org/en/ 具有事件驱动和非阻塞 I/O 的特点.
事件驱动是指 Node.JS 把每一个任务当成事件来处理.
非阻塞 I/O 是指 Node.JS 遇到 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
来源: http://www.bubuko.com/infodetail-3172418.html