事件循环(eventLoop
) 详解
并行和并发:
并行性是指两个或多个事件在同一时刻发生;
并发性是指两个或多个事件在同一时间段内发生。
任务队列 和 事件循环
JS 将变量存储在 和 中
堆中存放对象比如 :数组,对象,函数
栈中存放变量和对象的指针
运行时同步任务会排好队,依次执行,而排队的地方就叫执行栈(先进后出)
JS运行时,除了有一个主线程运行,引擎还提供任务队列
task queue
, 里面是各种需要当前程序处理的异步任务。(不止一个任务队列)异步任务通常是一个回调函数;如果一个异步任务没有回调函数,是不会放入异步任务队列中,同样也不会进入主线程中执行。
执行过程:
- 主线程会先执行完所有同步任务
- 同步任务结束,查看任务队列()里面的异步任务
- 查看所有满足执行条件的异步任务,放入主线程中变成同步任务,执行对应的回调函数。
- 执行完毕再将下一个满足执行条件的异步任务放入主线程中执行。
- 不断重复前三步操作
只要同步任务执行完毕,JS引擎会不停的监测检查任务队列中的异步任务,将满足执行条件且最先进入的任务加入到执行栈中去执行,如此往复。这种检查机制就叫事件循环 Event Loop
微任务与宏任务
异步任务还会分两层:
- 微任务(
micro task
) - 宏任务(
macro task
)
;
如果没有满足条件的任务,再去宏任务中查找;
如果有则将所有满足条件的微任务按顺序依次加入到主进程中;
执行完毕后再去红任务队列中查找,往复循环。
常见宏任务:
- 定时器 I/O(输入/输出)操作
微任务(jobs
):
- Promise async/await
nodeJS
nodeJS 中任务队列 加入了process.nextTick
和setImmediate
。
前者比后者先执行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});
setImmediate(function (){
setImmediate(function A() {
console.log(3);
setImmediate(function B(){console.log(4);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
});
// 1
// 2
// 3
// TIMEOUT FIRED
// 4
//这个结果是不确定的可能是 1234 TIMEOUT FIRED || 12 TIMEOUT FIRED 34
其二者的调用顺序取决于当前event loop的上下文,如果他们在异步i/o callback之外调用,其执行先后顺序是不确定的。1
2
3
4
5
6
7
8
9
10
11
12var fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout')
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
//immediate
//timeout
这是因为fs.readFile
callback执行完后,程序设定了timer
和 setImmediate
,因此poll阶段不会被阻塞进而进入check阶段先执行setImmediate,后进入timer阶段执行setTimeout。setTimeout和setImmediate的差异
process.nextTick在最新版nodejs中不被推荐使用,推荐使用setImmediate ,原因在于nextTick是在当前帧介绍后立即执行,会阻断I/O并且有最大数量限制;而setImmediate不会阻断I/O,更像是setTimeout(func, 0)