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/cangqinglang/p/8967268.html
https://www.cnblogs.com/onepixel/p/7143769.html