进程:独立运行,拥有资源空间的应用程序
线程:CPU调度的最小单位
浏览器: 多进程
浏览器有哪些进程?
-
Browser进程,也是主进程
- 负责各个页面的管理 创建 销毁
- 前进后退等
- 网络资源下载
-
插件进程:比如Chrome的各种插件
-
GPU进程
- 复杂3D的绘制
-
渲染进程(浏览器内核)
- 负责页面的渲染
- 脚本处理、事件执行等
-
网络进程
- 网络资源的加载
渲染进程
- GUI渲染线程
- 渲染浏览器界面
- 解析html dom
- 解析css
- 结合渲染树(rendering tree)
- 重绘(repaint):当元素样式的改变不影响页面布局时,比如元素的颜色,浏览器将对元素进行的更新,称之为重绘
- 回流(reflow):也叫做重排。当元素的尺寸或者位置发生了变化,就需要重新计算渲染树,这就是回流,比如元素的宽高、位置,浏览器会重新渲染页面,称为回流,又叫重排(layout)
注意: GUI线程和JS引擎线程是互斥的,同一时间只能执行一个
- JS 引擎线程
- 解析js脚本、运行js代码
- 任务队列
- 事件触发线程
- 控制事件循环 (task queue)
- 定时触发线程
- setTimeout setInterval
- 浏览器定时计数器 不是 js 引擎计数
- 异步请求线程
- XMLHttpRequest 回调
事件循环 Eventloop
- js 同步任务 和异步任务 同步任务主线程执行 形成执行栈
- 事件触发线程 任务队列
- 任务队列(Task)、微任务队列(Microtask)和宏任务队列(Macrotask)
浏览器事件循环
任务队列
任务队列(Task)、微任务队列(Microtask)和宏任务队列(Macrotask)
宏(普通)任务(macrotask)
可以将每次执行栈执行的代码当做是一个宏任务
宏任务(任务执行栈) =》 GUI 渲染 =》 宏任务(任务执行栈)
- I/O(Input/Output)
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame等
微任务(microtask)
当宏任务(任务执行栈)执行完,会在渲染前,将执行期间所产生的所有微任务都执行完
宏任务(任务执行栈) =》 微任务 =》 GUI 渲染 =》 宏任务(下一个任务执行栈) =》GUI 渲染
- process.nextTick
- MutationObserver
- Promise.then catch finally
整体流程:
- 执行一个当前执行栈同步任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务内的同步任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
事件循环机制
- 代码执行时,先执行同步任务,然后将异步任务放入任务队列中,等待执行。
- 当所有同步任务执行完毕后,JavaScript引擎会去读取任务队列中的任务。
- 将队列中的第一个任务压入执行栈中执行,执行完毕后将其出栈。
- 如此循环执行,直到任务队列中的所有任务都执行完毕。
Node 事件循环机制
为什么Nodejs 中任务队列要分阶段?
- 为了有效地管理和调度各种类型的异步任务,确保事件循环的高效运行。这种设计使得 Node.js 能够在单线程环境中有效地处理大量的并发任务
如果一个阶段中有产生了新的当前阶段的微任务,是在当前事件循环中执行吗?还是到下一次事件循环执行?
- 它将会在当前的事件循环中执行。事件循环的每个阶段都有自己的执行栈,微任务会在当前阶段的执行栈完成后执行
这个不是分阶段吗?那肯定是一个阶段内部的队列执行完毕之后,在执行下一个阶段的微任务队列,不也是阻塞的吗?
- Node.js是基于事件循环和非阻塞I/O模型的,这意味着在其执行过程中,会有多个阶段,但是并没有严格意义上的“阶”的概念
与浏览器事件循环的差异:
Node.js 的事件循环(Event Loop)是一个处理异步操作的机制,它会按照顺序依次执行不同阶段任务。事件循环机制中分为多个阶段,每个阶段都有自己的任务队列,包括:
- Timers 阶段:
- 处理 setTimeout 和 setInterval 调度的回调函数。
- 如果指定的时间到了,回调函数会被放入这个队列。
- Pending Callbacks 阶段:
- 处理一些 I/O 操作的回调,比如 TCP 错误类型的回调。
- 这些回调并不完全由开发者控制,而是由操作系统调度的。
- idle, Prepare 阶段:
- 仅供内部使用的阶段。
- Poll 阶段:
- 获取新的 I/O 事件,执行 I/O 回调函数。
- 通常情况下,这个阶段会一直等待,直到有新的 I/O 事件到来。
- Check 阶段:
- 处理 setImmediate 调度的回调函数。
- setImmediate 的回调会在这个阶段执行,比 setTimeout 更早。
- Close Callbacks 阶段:
- 处理一些关闭的回调函数,比如 socket.on(‘close’, …)。
多个队列的必要性
不同类型的异步任务有不同的优先级和处理方式。使用多个队列可以确保这些任务被正确地调度和执行:
- Timers 和 Poll 阶段的区别:
- setTimeout 和 setInterval 的回调在 Timers 阶段执行,这些回调函数依赖于计时器的到期时间。
- Poll 阶段处理大多数 I/O 回调,这是事件循环的主要阶段,处理大部分异步 I/O 操作。
- Immediate 与 Timeout 的不同:
- setImmediate 的回调函数在 Check 阶段执行,这是在当前事件循环周期结束后立即执行。
- setTimeout 的回调函数则是在 Timers 阶段执行,它可能会延迟到下一个事件循环周期,甚至更久。
- 处理关闭回调:
- Close Callbacks 阶段专门处理如 socket.on(‘close’) 这样的回调,以确保在资源释放时执行。
// example:
const fs = require('fs');
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
fs.readFile(__filename, () => {
console.log('I/O callback');
setTimeout(() => {
console.log('setTimeout inside I/O');
}, 0);
setImmediate(() => {
console.log('setImmediate inside I/O');
});
});
console.log('Synchronous log');
// 执行结果
Synchronous log
setImmediate
I/O callback
setImmediate inside I/O
setTimeout inside I/O
setTimeout
- Synchronous log 立即执行。
- setImmediate 回调会在 setTimeout 之前执行,因为 setImmediate 在当前循环结束后立即执行。
- I/O callback 在读取文件完成后执行(Poll 阶段)。
- 在 I/O 回调中,setImmediate 会先于 setTimeout 执行。