网站开发 企业宣传册,怎样弄一个自己的平台,厦门网站搜索优化,怎么做淘宝网站的网页在浏览器中,每个渲染进程都有一个主线程,主线程非常繁忙#xff0c;既要处理DOM#xff0c;又要计算样式#xff0c;还要处理布局#xff0c;同时还需要处理JavaScript任务以及各种输入事件。此时我们就需要一个系统来统筹调度这么多不同类型的任务在主线程中有条不紊地执行…在浏览器中,每个渲染进程都有一个主线程,主线程非常繁忙既要处理DOM又要计算样式还要处理布局同时还需要处理JavaScript任务以及各种输入事件。此时我们就需要一个系统来统筹调度这么多不同类型的任务在主线程中有条不紊地执行而这个统筹调度系统就是本文要介绍的事件循环系统。 前言
在浏览器中,每个渲染进程都有一个主线程,主线程非常繁忙既要处理DOM又要计算样式还要处理布局同时还需要处理JavaScript任务以及各种输入事件。此时我们就需要一个系统来统筹调度这么多不同类型的任务在主线程中有条不紊地执行而这个统筹调度系统就是本文要介绍的事件循环系统(Event Loop)。
读完本文希望你能明白 进程与线程的区别 最新的Chrome浏览器包括哪些进程? 浏览器与Node的事件循环(Event Loop)有何区别?
一、进程与线程
1.概念
我们经常说JavaScript是单线程执行的那到底什么是线程什么是进程
一个进程就是一个程序的运行实例。详细解释就是启动一个程序的时候操作系统会为该程序创建一块内存用来存放代码、运行中的数据和一个执行任务的主线程我们把这样的一个运行环境叫进程。
而线程是操作系统能够进行运算调度的最小单位。线程是不能单独存在的它是由进程来启动和管理的,在进程中使用多线程并行处理能提升运算效率。
我们通过以下这张图来加深对两者的理解: 进程好比图中的工厂有单独的专属自己的工厂资源。当一个进程关闭之后操作系统会回收进程所占用的内存。 线程好比图中的工人多个工人在一个工厂中协作工作工厂与工人是 1:n的关系。这意味着一个进程由一个或多个线程组成进程中的任意一线程执行出错都会导致整个进程的崩溃。 工厂的空间是工人们共享的这意味着一个进程的内存空间是共享的每个线程都可用这些共享内存。 多个工厂之间独立存在。这意味着进程之间的内容相互隔离。
2.多进程与多线程 多进程在同一个时间里同一个计算机系统中允许两个或两个以上的进程处于运行状态。
以最新的 Chrome 浏览器为例,我打开掘金编辑文章页面时,出现以下五个进程:1个网络进程、1个浏览器进程、1个GPU进程以及1个渲染进程共4个如果打开的页面有运行插件的话还需要再加上1个插件进程(下图有番茄闹钟插件)。 多线程程序中包含多个执行流即在一个程序中可以同时运行多个不同的线程来执行不同的任务也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
二、最新的 Chrome 进程架构
最新的Chrome浏览器包括1个浏览器Browser主进程、1个GPU进程、1个网络NetWork进程、多个渲染进程和多个插件进程。 接下来我们介绍下这些进程的功能: 浏览器进程。主要负责界面显示、用户交互、子进程管理同时提供存储等功能。 渲染进程。核心任务是将HTML、CSS 和JavaScript转换为用户可以与之交互的网页排版引擎Blink和JavaScript引擎V8都是运行在该进程中默认情况下Chrome 会为每个Tab标签创建一个渲染进程。出于安全考虑渲染进程都是运行在沙箱模式下。渲染进程中主要包含以下线程主线程(Main thread)、工作线程(Worker thread)、 排版线程 (Compositor thread)和光栅线程(Raster thread)。 GPU进程。其实Chrome刚开始发布的时候是没有GPU进程的。而GPU的使用初衷是为了实现3D CSS的效果只是随后网页、Chrome 的UI界面都选择采用GPU来绘制这使得GPU成为浏览器普遍的需求。最后Chrome在其多进程架构上也引入了GPU进程。 网络进程。主要负责页面的网络资源加载之前是作为一个模块运行在浏览器进程里面的直至最近才独立出来成为一个单独的进程。 插件进程。主要是负责插件的运行因插件易崩溃所以需要通过插件进程来隔离以保证插件进程崩溃不会对浏览器和页面造成影响。
页面中的大部分任务都是在渲染进程的主线程上执行这些任务包括了 渲染事件如解析 DOM、计算布局、绘制; 用户交互事件(如鼠标点击、滚动页面、放大缩小等); JavaScript脚本执行事件; 网络请求完成、文件读写完成事件。
那么,如何协调这些任务有条不紊地在主线程上执行呢? 这就需要事件循环系统(Event Loop)
三、浏览器中的 Event Loop
1.什么是Event Loop
通过使用消息队列我们实现了线程之间的消息通信。在Chrome中跨进程之间的任务也是频繁发生的那么如何处理其他进程发送过来的任务可以参考下图(来源极客时间) 消息队列是一种数据结构可以存放要执行的任务。它符合队列“先进先出”的特点也就是说要添加任务的话添加到队列的尾部要取出任务的话从队列头部去取。
从图中可以看出渲染进程专门有一个IO线程用来接收其他进程传进来的消息接收到消息之后会将这些消息组装成任务发送给渲染主线程。主线程从消息队列中读取事件这个过程是循环不断的所以整个的这种运行机制又称为Event Loop事件循环。
2.同步任务和异步任务 同步任务即可以立即执行的任务例如声明一个变量或者执行一次加法操作等。同步任务属于宏任务。 异步任务是不会立即执行的事件任务。异步任务包括宏任务和微任务。 浏览器端常见的宏任务包括:setTimeout、setInterval、script整体代码、 I/O 操作、UI 渲染等;
浏览器端常见的微任务包括:new Promise().then(回调)、MutationObserver(html5新特性) 等。
3.Event Loop 过程解析
一个完整浏览器端的 Event Loop 过程可以概括为以下阶段 一开始执行栈空,我们可以把执行栈认为是一个存储函数调用的栈结构遵循先进后出的原则。微任务队列空宏任务队列里有且只有一个 script 脚本整体代码。 全局上下文script 标签被推入执行栈同步代码执行。在执行的过程中会先判断是同步任务还是异步任务也会产生新的 macro-task 与 micro-task它们会分别被推入各自的任务队列里。同步代码执行完了script 脚本会被移出 macro 队列这个过程本质上是队列的 macro-task 的执行和出队的过程。 上一步我们出队的是一个 macro-task这一步我们处理的是 micro-task。但需要注意的是当 macro-task 出队时任务是一个一个执行的而 micro-task 出队时任务是一队一队执行的。因此我们处理 micro 队列这一步会逐个执行队列中的任务并把它出队直到队列被清空。宏任务队列可以有多个微任务队列只有一个。 执行渲染操作更新界面 检查是否存在 Web worker 任务如果有则对其进行处理 上述过程循环往复直到两个队列都清空
我们总结一下每一次循环都是一个这样的过程 当某个宏任务执行完后,会查看是否有微任务队列。如果有先执行微任务队列中的所有任务如果没有会读取宏任务队列中排在最前的任务执行宏任务的过程中遇到微任务依次加入微任务队列。栈空后再次读取微任务队列里的任务依次类推。
接下来我们看道例子来介绍上面流程
Promise.resolve().then((){
console.log(Promise1)
setTimeout((){ console.log(setTimeout2)
},0)
})
setTimeout((){
console.log(setTimeout1)
Promise.resolve().then((){ console.log(Promise2)
})
},0)
最后输出结果是Promise1setTimeout1Promise2setTimeout2 一开始执行栈的同步任务这属于宏任务执行完毕会去查看是否有微任务队列上题中存在(有且只有一个)然后执行微任务队列中的所有任务输出Promise1同时会生成一个宏任务 setTimeout2 然后去查看宏任务队列宏任务 setTimeout1 在 setTimeout2 之前先执行宏任务 setTimeout1输出 setTimeout1 在执行宏任务setTimeout1时会生成微任务Promise2 放入微任务队列中接着先去清空微任务队列中的所有任务输出 Promise2 清空完微任务队列中的所有任务后就又会去宏任务队列取一个这回执行的是 setTimeout2
四、Node 中的 Event Loop
1.Node简介
Node 环境下的 Event Loop 与浏览器环境下的 Event Loop并不相同。Node.js 采用 V8 作为js的解析引擎而I/O处理方面使用了自己设计的libuvlibuv是一个基于事件驱动的跨平台抽象层封装了不同操作系统一些底层特性对外提供统一的API事件循环机制也是它里面的实现下文会详细介绍。注本文中所介绍Node 环境中的 Event Loop是基于node10及其之前版本。 Node.js的运行机制如下: V8引擎解析JavaScript脚本。 解析后的代码调用Node API。 libuv库负责Node API的执行。它将不同的任务分配给不同的线程形成一个Event Loop事件循环以异步的方式将任务的执行结果返回给V8引擎。 V8引擎再将结果返回给用户。
2.六个阶段
其中libuv引擎中的事件循环分为 6 个阶段它们会按照顺序反复运行。每当进入某一个阶段的时候都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值就会进入下一阶段。 从上图中大致看出node中的事件循环的顺序
外部输入数据--轮询阶段(poll)--检查阶段(check)--关闭事件回调阶段(close callback)--定时器检测阶段(timer)--I/O事件回调阶段(I/O callbacks)--闲置阶段(idle, prepare)--轮询阶段按照该顺序反复运行... timers 阶段这个阶段执行timersetTimeout、setInterval的回调 I/O callbacks 阶段处理一些上一轮循环中的少数未执行的 I/O 回调 idle, prepare 阶段仅node内部使用 poll 阶段获取新的I/O事件, 适当的条件下node将阻塞在这里 check 阶段执行 setImmediate() 的回调 close callbacks 阶段执行 socket 的 close 事件回调
注意上面六个阶段都不包括 process.nextTick()(下文会介绍)
接下去我们详细介绍timers、poll、check这3个阶段因为日常开发中的绝大部分异步任务都是在这3个阶段处理的。
(1) timer
timers 阶段会执行 setTimeout 和 setInterval 回调并且是由 poll 阶段控制的。 同样在 Node 中定时器指定的时间也不是准确时间只能是尽快执行。
(2) poll
poll 是一个至关重要的阶段这一阶段中系统会做两件事情
1.回到 timer 阶段执行回调
2.执行 I/O 回调
并且在进入该阶段时如果没有设定了 timer 的话会发生以下两件事情 如果 poll 队列不为空会遍历回调队列并同步执行直到队列为空或者达到系统限制 如果 poll 队列为空时会有两件事发生 如果有 setImmediate 回调需要执行poll 阶段会停止并且进入到 check 阶段执行回调 如果没有 setImmediate 回调需要执行会等待回调被加入到队列中并立即执行回调这里同样会有个超时时间设置防止一直等待下去
当然设定了 timer 的话且 poll 队列为空则会判断是否有 timer 超时如果有的话会回到 timer 阶段执行回调。
(3) check阶段
setImmediate()的回调会被加入check队列中从event loop的阶段图可以知道check阶段的执行顺序在poll阶段之后。 我们先来看个例子:
console.log(start)
setTimeout(() {
console.log(timer1)
Promise.resolve().then(function() { console.log(promise1)
})
}, 0)
setTimeout(() {
console.log(timer2)
Promise.resolve().then(function() { console.log(promise2)
})
}, 0)
Promise.resolve().then(function() {
console.log(promise3)
})
console.log(end)
//startendpromise3timer1timer2promise1promise2 一开始执行栈的同步任务这属于宏任务执行完毕后依次打印出start end并将2个timer依次放入timer队列,会先去执行微任务这点跟浏览器端的一样所以打印出promise3 然后进入timers阶段执行timer1的回调函数打印timer1并将promise.then回调放入microtask队列同样的步骤执行timer2打印timer2这点跟浏览器端相差比较大timers阶段有几个setTimeout/setInterval都会依次执行并不像浏览器端每执行一个宏任务后就去执行一个微任务关于Node与浏览器的 Event Loop 差异下文还会详细介绍。
3.Micro-Task 与 Macro-Task
Node端事件循环中的异步队列也是分为macro宏任务队列和 micro微任务队列。 Node端常见的 macro-task 比如setTimeout、setInterval、 setImmediate、script整体代码、 I/O 操作等。 Node端常见的 micro-task 比如: process.nextTick、new Promise().then(回调)等。
4.注意点
(1) setTimeout 和 setImmediate
二者非常相似区别主要在于调用时机不同。 setImmediate 设计在poll阶段完成时执行即check阶段 setTimeout 设计在poll阶段为空闲时且设定时间到达后执行但它在timer阶段执行
setTimeout(function timeout () {
console.log(timeout);
},0);
setImmediate(function immediate () {
console.log(immediate);
}); 对于以上代码来说setTimeout 可能执行在前也可能执行在后。 首先 setTimeout(fn, 0) setTimeout(fn, 1)这是由源码决定的 进入事件循环也是需要成本的如果在准备时候花费了大于 1ms 的时间那么在 timer 阶段就会直接执行 setTimeout 回调 如果准备时间花费小于 1ms那么就是 setImmediate 回调先执行了
但当二者在异步i/o callback内部调用时总是先执行setImmediate再执行setTimeout
const fs require(fs) fs.readFile(__filename, () { setTimeout(() { console.log(timeout); }, 0) setImmediate(() { console.log(immediate) }) }) // immediate // timeout
在上述代码中setImmediate 永远先执行。因为两个代码写在 IO 回调中IO 回调是在 poll 阶段执行当回调执行完毕后队列为空发现存在 setImmediate 回调所以就直接跳转到 check 阶段去执行回调了。
(2) process.nextTick
这个函数其实是独立于 Event Loop 之外的它有一个自己的队列当每个阶段完成后如果存在 nextTick 队列就会清空队列中的所有回调函数并且优先于其他 microtask 执行。
setTimeout(() {
console.log(timer1)
Promise.resolve().then(function() { console.log(promise1)
})
}, 0)
process.nextTick(() {
console.log(nextTick)
process.nextTick(() { console.log(nextTick) process.nextTick(() { console.log(nextTick) process.nextTick(() { console.log(nextTick) }) })
})
})
// nextTicknextTicknextTicknextTicktimer1promise1
五、Node与浏览器的 Event Loop 差异
浏览器环境下microtask的任务队列是每个macrotask执行完之后执行。而在Node.js中microtask会在事件循环的各个阶段之间执行也就是一个阶段执行完毕就会去执行microtask队列的任务。 接下我们通过一个例子来说明两者区别
setTimeout((){ console.log(timer1) Promise.resolve().then(function() { console.log(promise1) }) }, 0) setTimeout((){ console.log(timer2) Promise.resolve().then(function() { console.log(promise2) }) }, 0)
浏览器端运行结果timer1promise1timer2promise2
浏览器端的处理过程如下 Node端运行结果
要看第一个定时器执行完第二个定时器是否在完成队列中。 如果是第二个定时器还未在完成队列中最后的结果为timer1promise1timer2promise2 如果是第二个定时器已经在完成队列中则最后的结果为timer1timer2promise1promise2(下文过程解释基于这种情况下)
1.全局脚本main()执行将2个timer依次放入timer队列main()执行完毕调用栈空闲任务队列开始执行
2.首先进入timers阶段执行timer1的回调函数打印timer1并将promise1.then回调放入microtask队列同样的步骤执行timer2打印timer2
3.至此timer阶段执行结束event loop进入下一个阶段之前执行microtask队列的所有任务依次打印promise1、promise2
Node端的处理过程如下 六、总结
浏览器和Node 环境下Event Loop有所区别主要体现在微任务队列的执行时机不同 Node端microtask 在事件循环的各个阶段之间执行 浏览器端microtask 在事件循环的 macrotask 执行完之后执行 阅读目录置顶)(长期更新计算机领域知识https://blog.csdn.net/weixin_43392489/article/details/102380691
阅读目录置顶)(长期更新计算机领域知识https://blog.csdn.net/weixin_43392489/article/details/102380882