浏览器渲染机制(一)浏览器的多进程

线程和进程


形象比喻:

  • 进程是一个工厂,工厂有它的独立资源

  • 工厂之间相互独立

  • 线程是工厂中的工人,多个工人协作完成任务

  • 工厂内有一个或多个工人

  • 工人之间共享空间


完善概念:

  • 工厂的资源 -> 系统分配的内存(独立的一块内存)

  • 工厂之间的相互独立 -> 进程之间相互独立

  • 多个工人协作完成任务 -> 多个线程在进程中协作完成任务

  • 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成

  • 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

巩固一下:

如果是windows电脑中,可以打开任务管理器,可以看到有一个后台进程列表。对,那里就是查看进程的地方,而且可以看到每个进程的内存资源信息以及cpu占有率。

在这里插入图片描述

所以,应该更容易理解了:进程是cpu资源分配的最小单位(系统会给它分配内存)

官方术语:

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

不同进程之间也可以通信,不过代价较大
现在,一般通用的叫法:单线程多线程,都是指在一个进程内的单和多。(所以核心还是得属于一个进程才行)


举个栗子:
平常我们在使用电脑过程中,假如我想登录微信,那么我双击微信图标登录即可。从表面上看,桌面打开了微信界面。实际上,系统也相应的开启了一个进程(一个程序可能会有多个进程,比如qq主进程,qq辅助进程等)。系统会给进程分配相应的cpu资源以及内存资源来保证进程运行。
在这里插入图片描述
我事先已经打开了谷歌浏览器,相应任务管理器也会有相对应的进程,以及该进程的cpu占用率、内存占用资源。

现在应该理解进程是什么意思了吧,那么线程又是什么鬼?

实际我们一个程序对应一个进程(这里直接按对应一个来举例),进程也可以细分即线程。比如我通过微信正在聊天,我想看看朋友圈,相应的微信进程会分配朋友圈线程来执行。即进程是种类,线程是种类下的具体列表。进程管理着线程。

理解了进程和线程,我们来看浏览器的进程。

浏览器是多进程的


简单理解:

  • 浏览器是多进程的
  • 浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
  • 简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。

在这里插入图片描述

图中打开了Chrome浏览器的多个标签页,然后可以在Chrome的任务管理器中看到有多个进程(分别是每一个Tab页面有一个独立的进程,以及一个主进程)。

图二:
在这里插入图片描述

在这里浏览器应该也有自己的优化机制,有时候打开多个tab页后,可以在Chrome任务管理器中看到,有些进程被合并了(所以每一个Tab标签对应一个进程并不一定是绝对的)

所以:

  1. 浏览器是多进程
  2. 不同类型的标签页都会开启一个新的进程
  3. 相同类型的标签页是会合并到一个进程

浏览器包含的进程

知道了浏览器是多进程后,再来看看它到底包含哪些进程:(为了简化理解,仅列举主要进程)

1. Browser进程浏览器的主进程(负责协调,主控),只有一个,作用有:

1.1、负责浏览器的界面显示,与用户交互,如前进,后退等
1.2、负责各个页面的管理,创建和销毁其它进程
1.3、将Rendered进程得到的内存中的Bitmap,绘制到用户界面上
1.4、网络资源的管理,下载

2.第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建。
3.GPU进程:最多一个,用于3D绘制和硬件加速等。
4.浏览器渲染进程(浏览器内核)(Render进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为:

页面渲染,脚本执行,事件处理等。(也就是负责HTML、CSS、JS等文件解析和执行)

强化记忆: 在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程)

浏览器有时会将多个进程合并(譬如打开多个空白标签页后,会发现多个空白标签页被合并成了一个进程),如图:(自己可以通过Chrome的更多工具 -> 任务管理器自行验证)
在这里插入图片描述

浏览器多进程的优势

1、避免单个page crash影响整个浏览器
2、避免第三方插件crash影响整个浏览器
3、多进程充分利用多核优势
4、方便使用沙盒模型隔离插件等进程,提高浏览器稳定性


简单理解 :
如果浏览器是单进程的,某个Tab页崩溃了,就影响了整个浏览器,体验就会很差。同理如果是单进程的,插件崩溃了也会影响整个浏览器;
当然,内存等资源消耗也会更大,像空间换时间一样。


浏览器渲染进程(浏览器内核)

重点来了,我们可以看到,上面提到了这么多的进程,那么,对于普通的前端操作来说,最终要的是什么呢?答案是渲染进程。因为页面的渲染,JS的执行,事件的触发,都在这个进程内进行的。接下来重点分析这个进程。


注意:浏览器是多进程的,浏览器的渲染进程是多线程的
浏览器渲染进程即浏览器内核,是浏览器的进程之一:主要作用:进行页面的渲染、脚本执行、事件处理等


浏览器渲染进程会开启多个线程协作完成,包括:GUI渲染线程,JS引擎线程,事件触发线程,定时器触发线程,异步HTTP请求线程。主要的线程以及作用如下:


1、GUI渲染线程

  • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
  • 当界面需要重绘或由于某种操作引发回流时,该线程就会执行。
    补充:当RenderObject树需要更新样式属性时,即发生重绘(Repaint);当RenderObject树中的元素规则尺寸,布局或显示隐藏等发生变化,即发生回流(reflow)。

注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

浏览器器内核拿到内容后,渲染大概可以划分成以下几个步骤:
在这里插入图片描述

  1. 解析HTML文件,构建DOM树,同时浏览器主进程负责下载CSS文件
  2. CSS文件下载完成,解析CSS文件成树形的数据结构,然后结合DOM树合并成RenderObject树
  3. 布局RenderObject树,负责RenderObject树中的元素的尺寸,位置等计算
  4. 绘制RenderObject树,绘制页面的像素信息
  5. 浏览器主进程将默认的图层和复合图层交给GPU进程,GPU进程再将各个图层合成(conposite),最后显示出页面

注意:
1.默认图层指的是出于普通文档流的元素
2.复合图层一般指的使用动画执行或者等元素,也可以使用z-index将层级高的元素变成复合图层,使用复合图层可以进行硬件加速,其原理是避免了默认图层的重绘和回流,想了解更深入介意自行研究

了解GUI渲染线程的执行过程。我们可以根据原理进行渲染优化:
1、尽可能早的提前引入css文件,例如在头部引入css文件。
2、尽可能早的加载css文件中的引入的资源,例如自定义字体文件,可以使用预加载,在link标签中加入rel=“preload” as = “font”该元素属性,不会造成渲染阻塞。
3、在DOM和CSS渲染之后加载js文件,例如在尾部加载js文件,或者使用该元素属性defer和async,进行js问价异步加载,但是不同的浏览器会有兼容性问题。


2、JS引擎线程

  • 也称为JS内核,负责 处理 JavaScript脚本程序。(例如常常听到的谷歌浏览器的V8引擎,新版火狐的JaegerMonkey引擎等)。
  • JS引擎线程负责 解析 avaScript脚本,运行代码。
  • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(render进程)中无论什么时候都只有一个JS线程在运行JS程序。(因为JS引擎是单线程的呀)

同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

浏览器内核:就是浏览器渲染进程(是浏览器包含的进程之一); JS内核:JS引擎线程(是浏览器渲染进程的线程之一)

JS引擎为什么一定是是单线程的??

js是单线程的。也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

  • JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题(譬如多线程操作一般要加锁)。

比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

3、事件触发线程


  • 属于浏览器(准确的说归属于浏览器渲染进程)而不是JS引擎,用来控制事件循环(可以理解成JS引擎自己都忙不过来,需要浏览器另开线程协助)。

    浏览器渲染进程是浏览器众多进程之一
    JS引擎线程是浏览器渲染进程的众多线程之一

  • 当JS引擎执行代码块如setTimeout时(也可来自浏览器内核的其它线程,如鼠标点击,AJAX异步请求等),会将对应任务添加到事件线程中。

  • 当对应的事件符合触发条件被触发时,该线程会 把事件添加到待处理队列的队尾,等待JS引擎的处理。

注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)。

js事件轮询

上面我们已经知道JS引擎是单线程,任务应该是按顺序执行的,那么怎么会有同步异步之说?

  • 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
  • 如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
  • JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
所谓挂起一个任务,就是暂停这个任务运行,它仍然占用一定的内存空间,有可能对CPU也在占用着.
  • 于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

理解了同步异步。其实其最本质原因就是基于js的事件轮询机制

  1. 所有同步任务都在主线程(即js引擎线程)上执行,形成一个执行栈

  2. 而异步任务均由事件触发线程控制,其有一个任务队列。只要异步任务有了运行结果,就在"任务队列"之中放置回调事件

    异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。所以所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。

  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",按顺序结束等待状态,进入执行栈,开始执行。

  4. 主线程不断重复上面的第三步

  5. 只要主线程空了,就会去读取"任务队列",这个过程会不断重复。这就是JavaScript的运行机制。又称为Event Loop(事件循环或者轮询)。


4、定时器触发线程

上述事件循环机制的核心是:JS引擎线程事件触发线程

  1. JS引擎线程:控制主线程
  2. 事件触发线程:控制任务队列进入主线程

问题来啦:为什么要单独的定时器线程嘞?

因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时

问题来啦:什么时候会用到定时器线程呢?

当使用setTimeout或setInterval时,它需要定时器线程计时,计时完成后就会将特定的事件推入事件触发线程的任务队列中。等待进入主线程执行。

举个栗子:

setTimeout(function(){
    console.log('hello!');
}, 1000);

这段代码的作用是当1000毫秒计时完毕后(由定时器线程计时),将回调函数推入事件队列中,等待主线程执行


setTimeout(function(){
    console.log('hello!');
}, 0);

console.log('begin');

//begin hello

这段代码的效果是表示当前代码执行完console.log('begin');(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。


注意:

  1. 虽然代码的本意是0毫秒后就推入事件队列,但是html5标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
  2. 就算不等待4ms,就算假设0毫秒就推入事件队列,也会先执行begin(因为只有主线程可执行栈内空了后才会主动读取事件队列)。
  3. 要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
  4. 同理setInterval则是每次都精确的隔一段时间推入一个事件(但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了)

5、异步http请求线程

  • XMLHttpRequest在连接后是通过浏览器新型一个线程请求
  • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JavaScript引擎执行

总结下来,如下:

在这里插入图片描述

注意:

  1. 渲染进程包括的线程:GUI渲染线程、JS引擎线程、事件触发线程、定时器触发线程、异步http请求线程

总结:js引擎执行的第三个阶段–执行阶段,在分析之前我们先思考以下两个问题:

1、js是单线程的,为了避免代码解析阻塞使用了异步执行,那么它的异步执行机制是怎么样的?

答:通过事件循环(Event Loop)


2、js是单线程的,那么是否代表参与js执行过程的线程就只有一个?

答:不是的,会有四个线程参与该过程,但是永远只有JS引擎线程在执行JS脚本程序,其他的三个线程只协助,不参与代码解析与执行。参与js执行过程的线程分别是:

(1)JS引擎线程: 负责解析执行Javascript脚本程序的主线程。

(2)事件触发线程: 主要用于控制事件(例如鼠标,键盘等事件),当该事件被触发时候,事件触发线程就会把该事件的处理函数推进事件队列,等待JS引擎线程执行。

(3)定时器触发线程:主要控制计时器setInterval和延时器setTimeout,用于定时器的计时,计时完毕,满足定时器的触发条件,则将定时器的处理函数推进事件队列中,等待JS引擎线程执行。

(4)HTTP异步请求线程:通过XMLHttpRequest连接后,通过浏览器新开的一个线程,监控readyState状态变更时如果设置了该状态的回调函数,则将该状态的处理函数推进事件队列中,等待JS引擎线程执行。

注:浏览器对同一域名请求的并发连接数是有限制的,Chrome和Firefox限制数为6个,ie8则为10个。

牢记:永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进事件队列,等待JS引擎线程执行。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值