什么是浏览器的多进程和多线程?
我们要理解浏览器进程和线程之间的关系,首先要了解什么是进程?什么是线程?
进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
示例
打开谷歌浏览器,随便打开几个网页,按下shift esc键,界面将显示目前浏览器启动的几个进程,还有相应的进程内存占用空间、CPU核数、进程ID等等。
在浏览器中新打开一个tab页,浏览器会开启一个新的进程,但是,这并不意味着,浏览器上每一个tab页就是一个进程,因为
有的页面是通过链接跳转打开的,熟悉前端的朋友知道html里的a标签
可以新开一个tab页,这新开的和原先的属于同一进程。
浏览器进程(Browser进程)
浏览器的主进程(负责协调、主控),只有一个。作用有
- 负责浏览器界面显示,与用户交互。如前进,后退等
- 负责各个页面的管理,创建和销毁其他进程
- 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
- 网络资源的管理,下载等
GPU进程:
最多一个,用于3D绘制等,可禁止掉。
浏览器渲染进程(Renderer进程,内部是多线程的)
每一个标签页的打开都会创建一个浏览器渲染进程(浏览器内核)。默认每个Tab页面一个进程,互不影响。主要作用为页面渲染,脚本执行,事件处理等
线程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
一个程序运行会启动多个进程,而进程里又包含了多个线程,多个线程为进程服务。
下图为浏览器渲染进程中存在的各个线程
GUI渲染线程
负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
JS引擎线程
也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
JS引擎线程负责解析Javascript脚本,运行代码。
JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
事件触发线程
归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
定时触发器线程
传说中的setInterval与setTimeout所在线程
浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
异步http请求线程
在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。
浏览器内核中线程之间的关系
GUI渲染线程与JS引擎线程互斥
因为JS引擎可以修改DOM树,那么如果JS引擎在执行修改了DOM结构的同时,
GUI线程也在渲染页面,那么这样就会导致渲染线程获取的DOM的元素信息可能与JS引擎操作DOM后的结果不一致。
为了防止这种现象,GUI线程与JS线程需要设计为互斥关系,当JS引擎执行的时候,GUI线程需要被冻结,
但是GUI的渲染会被保存在一个队列当中,等待JS引擎空闲的时候执行渲染。由此也可以推出,
如果JS引擎正在进行CPU密集型计算,那么JS引擎将会阻塞,长时间不空闲,导致渲染进程一直不能执行渲染,
页面就会看起来卡顿卡顿的,渲染不连贯,所以,要尽量避免JS执行时间过长。
JS引擎线程与事件触发线程、定时触发器线程、异步HTTP请求线程
事件触发线程、定时触发器线程、异步HTTP请求线程三个线程有一个共同点,那就是使用回调函数的形式,
当满足了特定的条件,这些回调函数会被执行。这些回调函数被浏览器内核理解成事件,在浏览器内核中拥有一个事件队列,
这三个线程当满足了内部特定的条件,会将这些回调函数添加到事件队列中,等待JS引擎空闲执行。例如异步HTTP请求线程,
线程如果检测到请求的状态变更,如果设置有回调函数,回调函数会被添加事件队列中,等待JS引擎空闲了执行。
但是,JS引擎对事件队列(宏任务)与JS引擎内的任务(微任务)执行存在着先后循序,当每执行完一个事件队列的时间,
JS引擎会检测内部是否有未执行的任务,如果有,将会优先执行(微任务)。