进程和线程

先来复习一下进程和线程的概念

  • 进程是CPU资源分配的最小单位
  • 进程之间相互独立
  • 一个进程由一个或多个线程组成
  • 线程是CPU调度的最小单位
  • 多个线程相互协作完成进程的任务
  • 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

浏览器是多进程的

浏览器之所以能够运行,是因为系统给它的进程分配了资源(CPU、内存)

可以简单理解为每打开一个Tab页面,就相当于创建了一个独立的浏览器进程,但浏览器有优化机制,有些进程可能会被合并,比如打开多个空白标签页可能会被合并为一个进程,但我试过发现并没有。。

image.png

多进程的优势

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

当然,多线程会使内存等资源消耗更大,有空间换时间的思想

浏览器主要包含哪些进程

Browser进程

浏览器的主进程(负责协调、主控)

作用大概如下:

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

第三方插件进程

每种类型的插件对应一个进程,仅当使用该插件时才创建

GPU进程

最多一个,用于3D绘制等

Renderer进程(浏览器内核)

对于普通的前端操作来说,最终要的就是渲染。这是重点

该进程拥有多个线程,这些线程共同来完成页面的渲染任务。

看看都有哪些线程

GUI渲染线程

  • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等
  • 当界面需要重绘或回流时,该线程就会执行
  • 该线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

JS引擎线程

  • 也称JS内核,负责处理JS脚本程序(如V8引擎)
  • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序

事件触发线程

  • JS脚本的执行不会影响到html元素事件的触发,由于JS单线程关系,会将触发后需要执行的JS脚本添加到JavaScript引擎的处理队列中,当JavaScript引擎空闲时才会去执行。
  • 注意,该线程只是触发,触发后要执行的代码依然要放到JS引擎线程中去执行。

定时触发器线程

  • setTimeoutsetInterval所在的线程。
  • 由于JavaScript引擎是单线程的,如果处于阻塞线程状态势必会影响计时的准确性,所以浏览器中的定时器并不是由JavaScript引擎来计数的。
  • 该线程只是计时,一旦计时完毕后,会将触发的脚本添加到JavaScript引擎的处理队列中,等待JavaScript引擎空闲后再执行。
  • 注意,W3CHTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms

异步HTTP请求线程

  • 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
  • 当状态发生变化时,如果之前有设置回调,会将这个回调再放入JavaScript引擎的处理队列中,再由JavaScript引擎执行。

Browser进程和Renderer进程的通信过程

  • Browser进程收到用户请求,首先需要获取页面内容(譬如通过网络下载资源),随后将该任务通过RendererHost接口传递给Render进程
  • Renderer进程的Renderer接口收到消息,简单解释后,交给渲染线程,然后开始渲染
    • 渲染线程接收请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程来帮助渲染
    • 可能会有JS线程操作DOM(这样可能会造成回流并重绘)
    • 最后Render进程将结果传递给Browser进程
  • Browser进程接收到结果并将结果绘制出来

Renderer进程中线程之间的关系

GUI渲染线程与JS引擎线程互斥

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起, GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

JS阻塞页面加载

从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。

工作者线程,JS的多线程?

Worker 接口是 Web Workers API 的一部分,指的是一种可由脚本创建的后台任务,任务执行中可以向其创建者收发信息。要创建一个 Worker 只须调用 Worker(URL) 构造函数,函数参数 URL 为指定的脚本。

Worker 也可以创建新的 Worker,当然,所有 Worker 必须与其创建者同源(注意:Blink暂时不支持嵌套 Worker)。

需要注意的是,不是所有函数和构造函数(或者说…类)都可以在 Worker 中使用。具体参考页面 Worker 所支持的函数和类。Worker 可以使用 XMLHttpRequest 发送请求,但是请求的 responseXMLchannel 两个属性值始终返回 nullfetch 仍可正常使用,没有类似的限制)。

  • 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
  • JS引擎线程与worker线程间通过特定的方式通信(postMessage ,需要通过序列化对象来与线程交互特定的数据)

如果有非常耗时的工作,单独开一个Worker线程,这样里面不管如何翻天覆地都不会影响JS引擎主线程, 只待计算出结果后,将结果通信给主线程即可

JS引擎是单线程的,这一点的本质仍然未改变,Worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。