一、进程与线程
1.进程:进程是CPU分配资源的最小单位
2.线程:线程是CPU调度的最小单位
3.进程与线程的关系:一个进程中可包含一个或多个线程,一个线程只能属于一个进程,进程中的多个线程共享该进程中的资源
4.个人理解:我们可以将进程看作是正在运行的应用程序,线程看作是这个应用程序的代码的执行器,也就是说线程是跑在进程里的,当我们运行应用程序时,CPU向该应用程序的进程分配资源,然后进程内部的线程对该进程的资源进行调度,从而完成应用程序的功能。
二、多进程的浏览器
Browser process(浏览器进程)
1.个数:1
2.作用:
- 浏览器的主进程,与用户进行交互(如标签页的前进、后退等)
- 负责各个页面的管理,创建和销毁进程
Renderer process(渲染进程)
1.个数;默认一个tab页面一个渲染进程,互不影响,渲染进程是多线程的
2.作用:
- 页面渲染
- 脚本执行
- 事件处理等
3.当我们打开一个页面时,相当于创建了一个渲染进程
网络请求进程
1.主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
第三方插件进程
1.个数:每种类型的插件对应一个进程,仅当使用该插件时才创建
2.作用:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
GPU进程
1.最多一个,用于3D绘制等
最后用其他博主的照片帮助大家理解
所以当我们打开 1 个页面至少需要 1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程,共 4 个;如果打开的页面有运行插件的话,还需要再加上 1 个插件进程。
几大进程之间的关系
当我们在地址栏输入URL时,浏览器进程创建网络请求进程,网络请求进程去服务器获取URL对应的HTML,然后将HTML交给Renderer进程,Renderer进程对HTMl进行解析,资源中遇到网络请求创建网络请求进程进行资源请求,如果需要插件则通知Browser Process,需要Plugin Process加载插件资源,执行插件代码,Renderer解析HTML结束后,Renderer Process计算得到图像帧,并将这些图像帧交给GPU Process,GPU Process将其转化为图像,最后Browser进程将页面完整展示在屏幕上。
多进程的优势
相比于单进程浏览器,多进程有如下优点:
- 避免单个page crash影响整个浏览器
- 避免第三方插件crash影响整个浏览器
- 多进程充分利用多核优势
- 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性
- 简单点理解:如果浏览器是单进程,那么某个Tab页崩溃了,就影响了整个浏览器,体验有多差;同理如果是单进程,插件崩溃了也会影响整个浏览器;而且多进程还有其它的诸多优势
- 但是,多线程也会导致内存等资源消耗也会更大
三、浏览器的Renderer process(渲染进程)
GUI渲染线程
- 负责渲染浏览器界面,解析HTML、CSS构建DOM树和Render树、布局、绘制等
- 当页面发生回流和重绘时,此线程也会运行
- 注意:GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
JS引擎线程(JS内核)
- JS引擎线程负责解析Javascript脚本,运行代码
- JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个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引擎执行。
Browser进程和Renderer进程的通信过程
-
浏览器进程接收到用户,首先需要获取页面内容(譬如通过网络下载资源),随后将该任务通过RendererHost接口传递给Render进程
-
Renderer进程的Renderer接口收到消息,简单解释后,交给渲染线程,然后开始渲染
渲染线程接收请求,加载网页并渲染网页,这其中可能需要网络请求进程获取资源和需要GPU进程来帮助渲染
当然可能会有JS线程操作DOM(这样可能会造成回流并重绘)
最后Render进程将结果传递给Browser进程 -
Browser进程接收到结果并将结果绘制出来
JS内核中线程之间的关系
我们知道JS是可以操作DOM元素的,如果我们在操作DOM元素的同时进行页面的渲染那么就会出现GUI线程渲染前后数据的不一致,为了防止渲染出现不可预期的结果才有了我们之前所说的,当JS引擎执行时,GUI线程会被挂起,GUI更新则会被保存在一个队列中,直到JS引擎执行完毕后,GUI线程才开始执行
JS阻塞页面加载
由上述我们可知,如果JS引擎一直执行那么,我们的GUI线程就会一直被挂起,那么就会出现页面加载不连贯的问题,这对用户来说是不友好的,而一般导致JS持续执行的多为巨量运算,当JS引擎正在进行巨量的计算时,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。
WebWorks
Webworks是HTML5的新特性,MDN的官方解释是:
Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面
一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件
这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window
因此,使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误
通俗理解就是,WebWorks是浏览器内核进程为了防止JS引擎执行巨量运算导致执行时间过长的问题,为它找的一个帮手,当JS引擎遇到巨量运算时,浏览器内核会创建一个WebWorks来执行js代码,而浏览器内核继续执行其他代码,当WebWorks执行结束后通过特定的方式通信(postMessage API,后续的文章中会提到),将执行结果返回给JS引擎
WebWorker与SharedWorker
- WebWorker只属于某个页面,不会和其他页面的Render进程(浏览器内核进程)共享,所以Chrome在Render进程中(每一个Tab页就是一个render进程)创建一个新的线程来运行Worker中的JavaScript程序。
- SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用,所以Chrome浏览器为SharedWorker单独创建一个进程来运行JavaScript程序,在浏览器中每个相同的JavaScript只存在一个SharedWorker进程,不管它被创建多少次。
- 本质上就是进程和线程的区别。SharedWorker由独立的进程管理,WebWorker只是属于render进程下的一个线程
基于Chrome浏览器的GUI线程渲染流程(后续更新)
从Event Loop谈JS的运行机制(后续更新)
参考(博主认为比较好的文章):
从浏览器多进程到JS单线程
「一道面试题」输入URL到渲染全面梳理中-页面渲染篇