blog
blog copied to clipboard
浏览器系列 - Javascript单线程与浏览器多进程
前言
-
进程是CPU资源分配的最小单位,是能拥有资源和独立运行的最小单位。
-
线程是CPU调度的最小单位,线程是建立在进程的基础上的一次程序运行,一个进程可以有多个线程。
-
不同进程之间也可以通信,不过代价比较大。
常说的单线程
、多线程
都是指在一个进程内的单和多,也就是说,敲黑板...这些多线程单线程都必须属于一个进程才行。
浏览器多进程
浏览器是多进程的,新增一个tab页面至少新增一个进程,然后操作系统会给每个进程都分配CPU和内存,一个浏览器可以有多个tab页面
那么整体来说,OS就给一个浏览器开启了多个进程,如下图
相比较单进程浏览器,多进程浏览器有以下优势:
- 避免单个
tab
页面阻塞运行,甚至crash
的话也整个浏览器也都跟着崩溃 - 避免第三方插件引起的
crash
导致整个浏览器崩溃 - 多进程充分利用多核
CPU
优势 - 方便使用沙盒模式隔离插件等进程,提高浏览器稳定性
简而言之,就是几大功能模块都使用单独的进程去运作,各个功能之间尽量不要相互影响。
渲染线程
我们作为 FEer 最关注的其实是渲染进程
,因为它包括了我们开发、优化需要用到的
页面渲染 :arrow_right: JS执行 :arrow_right: 事件的循环 :arrow_right:
首先我们牢记一点,浏览器的渲染进程
是多线程
的。
浏览器的渲染进程包括了以下线程:
GUI渲染线程
- 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制。
- 当界面需要Repaint或者reflow的时候,该线程就会执行
JS引擎线程
- 也称之为JS内核,负责处理
Javascript
/脚本程序 - JS引擎负责解析Javascript脚本,运行代码。
- JS引擎一直等待着任务队列中的任务到来,然后加以处理,一个
Tab页
中无论什么时候都只有一个JS线程
运行JS程序。
注意
GUI
渲染线程与JS
线程引擎是互斥的,当JS
引擎执行的时候,GUI
线程会被挂起,相当于被冻结了,GUI
更新会被保存在一个队列中,等到JS
引擎空闲的时候立即被执行。
所以 Javascript 脚本执行的时间过长,这样就会造成渲染不连贯,导致页面渲染加载阻塞。
事件触发线程
- 事件触发线程,注意要区别于JS引擎,用来控制事件循环。
- 当引擎执行到了宏任务和微任务(
setTimeout
用户点击
nextClick
)的时候,JS引擎就会吧这些任务交给事件触发线程
中。 - 当对应的事件符合条件被触发时,该线程会把事件添加到待处理的队列的队尾,等待JS引擎的处理。
- 注意,由于JS单线程的机制,所以这些等待处理的异步操作会等到JS引擎空闲的时候才会去执行。(
eventLoop
)
定时器触发线程
- 专门用于
setInterval
和setTimeout
所在的线程 - 浏览器开启定时器后,倒计时并不是由Javascript引擎自己计数的,因为单线程的原因,JS引擎本身做好自己的代码运行工作就好,还要来倒计时,真实分身乏术啊。
- 所以定时器触发线程的作用就是用来帮忙计数的,计数完成后,将回调函数推入事件队列中,等待主线程执行。
异步http请求线程
- 在
XMLHttpRequest
在两级后通过浏览器新开一个线程进行发起请求。 - 将检测到状态变动的时候,我们常常会设置一些成功或者失败的回调函数,状态发生变化之后,http请求线程就会将这些回调代码交给 Javascript 引擎执行。
从细微解释现象
网页渲染过程
用户打开了一个浏览器,会默认打开一个空的tab页面,此时与浏览器相关的会有:浏览器主进程
和 这个tab的渲染进程
-
浏览器输入
url
,浏览器主进程接管,开一个下载进程。 -
进行http请求,等待响应,获取内容。
-
随后将内容,通过
RenderHost
传递给Renderer
进程,浏览器渲染渲染进程开始工作。 -
解析
html
建立DOM
树 -
解析
css
,将CSS
代码解析成属性的数据及结构,然后结合DOM
合并成render
树 -
布局 render 树,重绘和重排,负责各元素尺寸、位置的计算。
-
浏览器会将各层的信息发送给
GPU
,GPU
会将各层合成显示在屏幕上。 -
最后,渲染进程将渲染结果传递回给,浏览器主进程,主进程再将结果绘制出来。
UI渲染和JS代码运行相互阻塞吗?
面试中常常会问到,浏览器再加载JS
代码的时候,页面UI
渲染是否停止?重绘重排呢?
今天,我们知道了GUI渲染线程与JS引擎线程是互斥的
,因为Javascript
线程中可以操作DOM
元素的,若果在JS
修改这些元素的属性的同时渲染界面,那么渲染现成前后获得的元素数据可能就不一样了。
因此为了防止渲染会出现不同的结果,那么浏览器内核就将UI渲染
(包括重绘和重排版)与JS引擎执行JS代码设置为互斥的关系,GUI
的更新一般会被暂时的存放到一个异步的队列中,等待JS
引擎空闲的时候再被执行(这里应该理解为GUI
变动被渲染引擎执行)。
浏览器 | 渲染引擎 | JS引擎 |
---|---|---|
firefox | gecko | monkey |
IE | Trident | Chakra |
edge | edge | Chakra |
Opera | Presto | Carakan |
chrome | webkit | V8 |
safari | webkit | SquirrelFish |
从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。 所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
css加载是否阻塞DOM树渲染
css是由http下载线程单独的下载线程异步下载,所以不会阻塞DOM树的解析。
但css
解析,会阻塞render
树进行渲染
- 因为render树是由DOM树解析和CSS解析后一同形成的。
- 因为加载css的时候,可能会修改下面DOM节点的样式,若不阻塞render树渲染,那么render树可能又得重新回流了。
WebWorker 与 SharedWorker
单页面的WebWorker
在红宝书学习webworker
的时候,记住了一句
webworker
是一个独立于widnow
外的一个独立作用域,webworker
仅仅拥有window对象上的部分属性和方法,特别是没有操作DOM的能力。而且只能透过postMessage这种特殊的方式与主JS引擎线程进行传递信息
以上是回忆...不是原文:joy:
浏览器创建worker
,分配一个单独子线程,受控于主线程,而且不能够操作DOM,自然也不会与GUI渲染线程相互阻塞了。所以我们一般会使用webworker
去做一些大量耗时的计算,然后通过postMessage()
方法传回拥有window
对象的JS引擎线程中去。并且悄悄补一句...postMessage
是``异步任务,属于
宏任务`,自然更不存在阻塞的问题了..
多页面共享 SharedWorker
要说WebWorker是服务于一个渲染进程内的多个线程...帮助他们(JS引擎线程,GUI渲染线程)减缓压力。
那么SharedWorker
就是一个浏览器内,多个tab
页的多个进程的帮手,要实现服务于多个进程之间,那么SharedWorker
本身也是一个依托于一个进程。
每开启一个浏览器,则多个页面会共享这个SharedWorker
对象。
参考文章
[2] V8与字节码