viviier

event-loop

当主线程的execution context stack空了,就会去读取task queue,如果task queue有task,那么就会通过event loop把task推进heap stack,这个过程会不断重复。这个javascrit的事件循环机制,就叫event looop。

javacript单线程

javascript是单线程的。javascript的主要用途是与用户互动、操作DOM,为了避免复杂性和一些不必要的问题,所以是从一诞生,javascript就是单线程。如果有两个线程,那么一个线程在某个DOM上添加内容,而另一个线程删掉了这个DOM,那么浏览器就不知道以哪个线程为准了。

同步和异步

同步任务是指在主线程的execution context stack执行的task,排队执行,只有当前一个task执行完毕,才能执行后一个任务。异步任务是指在task queue里的任务,当execution context stack空时,会通过event loop把task queue的任务push到execution context stack。
而异步任务有了运行结果后,一般会被push到task queue,一旦ecs(execution context stack)中的所有的task执行完毕,system就会通过event loop读取task queue,然后把task放入ecs开始执行。

task quque里有什么

task queue一般用来存放timers、i/o、callback(包含eventlistener)。这里仅指javascript,node的task queue在后面。

关于event loop

主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

event-loop

browser javascript中的event loop

browser javascript中的event loop一般用来触发callback、ajax、timers等等异步任务,而timers中主要是setTimeout。setTimeout会把事件添加到task quque的尾部,要等到ecs和tq(task queue)现有的事件都处理完才会执行。setTimeout(fn, 0)可以指定某个任务在ecs为空后立即执行指定的callback。

Node.js的Event loop

关于Node.js如何解析js代码

nodejs-event-loop-a

首先通过用户键入node的javascript代码,然后v8开始解析(是否解析为c++代码?),解析之后调用node的api返回执行结果给v8,v8再将结果返回给用户。

Node的event loop

nodejs-event-loop-b

v8在解析的时候,有一个libuv库负责node api的执行。它将不同的task分配给相对应的工作流(event queue || worker threads || …),形成一个event loop,以异步的方式将任务的执行结果返还给v8引擎。

定时器

node在语言标准上新增了两个’定时器’,process.nextticksetImmediate

小测试

1
2
3
4
5
6
// test.js
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
(() => console.log(5))();

process.nexttick 是将事件放入本轮循环nextTickQueue
setImmediate 是将事件放入次轮循环的task queue队尾
两个最大的不同是,process.nexttick无论有多少个都是将事件放入本轮循环通过当前event loop一次执行完,而多个setImmediate则需要多次loop才能执行完。所以前者要比后者发生的早,执行效率也高(不用检查task queue)。
but,如果像这样:

1
2
3
process.nextTick(function foo() {
process.nextTick(foo);
});

那么execution context stack根本不会去读取task queue,因为他一直在重复读取当前的nextTickQueue,递归的process.nexttick,Node.js会警告你改成setImmediate。

同步和异步

nodejs-event-loop-d

本轮循环、次轮循环

在Node中,异步任务分两种。

  • 追加在本轮循环的异步任务
  • 追加在次轮循环的异步任务

process.nexttickPromise 的回调函数,追加在本轮循环,即node的ecs一旦执行完成,就开始执行他们。

nodejs-event-loop-d

setTimeoutsetIntervalsetImmediate 的回调函数,追加在次轮循环

所以上面小测试代码执行顺序为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 下面两行,次轮循环执行
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
// 下面两行,本轮循环执行
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
// 同步任务
(() => console.log(5))();
// 也是根据代码的顺序来的
5
3
4
1
2

process.nextTick(nextTickQueue)

node执行完所有的同步任务,接下来就会执行process.nextTick队列。如果你希望异步任务尽快的执行,就使用process.nextTick

nodejs-event-loop-e

Promise(microTaskQueue)

Promise对象的回调函数,会进入异步任务微任务(microTask)队列。微任务队列追加在nextTickQueue后面,也属于本轮循环。

nodejs-event-loop-f

只有前一个队列全部清空以后,才会执行下一个队列。
所以本轮循环的执行顺序就是:

  • 同步任务
  • process.nextTick(nextTickQueue)
  • Promise(microTaskQueue)

事件循环的概念

Node.js只有一个主线程,事件循环是在主线程上完成的。

“When Node.js starts, it initializes the event loop, processes the provided input script which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.”

Node在开始执行脚本时,会先进行event loop的初始化,但是这是event loop还没开始,会先完成下面的事情👇:

  • 同步任务
  • 发起异步请求
  • 规划定时器生效时间
  • 执行process.nextTick等等

上面的这些事情执行完了,event loop就正式开始了。

六个阶段

事件循环会无限次的执行,一轮又一轮。只有异步任务的回调函数队列清空了,才会停止执行。每一轮的事件循环,分成六个阶段:

  • timers
  • I/O callbacks
  • idle,prepare
  • poll
  • check
  • close callbakcs

每个阶段都有一个先进先出的回调函数队列。只有一个阶段的回调函数队列清空了,该执行的回调函数都执行了,事件循环才会进入下一个阶段。

nodejs-event-loop-f

timers

定时器阶段,处理setTimeoutsetInterval的回调函数。进入这个阶段后,主线程会检查一下当前时间,是否满足定时器的条件。如果满足就执行回调函数,否则就离开这个阶段。

I/O callbacks

除了以下操作的回调函数,其他的回调函数都在这个阶段执行。

  • setTimeout()和setInterval()的回调函数
  • setImmediate()的回调函数
  • 用于关闭请求的回调函数,比如socket.on(‘close’, …)
idle,prepare

libuv内部使用。

poll

轮询时间。用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。

这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。

check

执行setImmediate的回调函数。

close callbacks

执行关闭请求的回调函数,比如socket.on('close', ...)

![nodejs-event-loop-g]

写在最后

综上,了解了event loop可以提高你对Javascript 运行机制的理解。javascript中的event loop一般用来触发callback、ajax、timers等等异步任务,主要是setTimeout 以及各种callback。node的event loop则更为复杂timers、i/o callback、poll…。

microtasks and macrotasks

examples of microtasks:

  • process.nextTick
  • promise
  • object.observe

examples of macrotasks:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O

参考链接

------本文结束感谢阅读------