浏览器多进程、多线程、运行机制

1,021 阅读7分钟

进程和线程

进程
cpu 分配资源的最小单位

线程
cpu 调度的最小单位

浏览器是多进程的

  • 每打开一个tab 页,就相当于创建了一个独立的浏览器进程,根据浏览器的优化机制不通,多个tab 页可以合并为一个进程

浏览器包含哪些进程

浏览器打开一个网页相当于新开了一个进程,进程有自己的多线程

  • Browser 进程(浏览器的主进程,只有一个)
    • 页面显示,与用户交互,比如前进、后退
    • 管理各个页面,创建和销毁其他进程
    • 网络资源管理,比如下载
  • 第三方插件进程
    • 每个类型的插件对应一个进程,进党使用插件时才创建
  • GPU 进程
    • 最多一个,用于3D 绘制
  • 浏览器渲染进程(浏览器内核)
    • 默认每个tab 页一个进程,互不影响
    • 页面渲染,JS脚本之行,事件处理等

浏览器多进程的优势

如果浏览器是但进程的,那某个tab 页崩溃了,会影响整个浏览器

浏览器内核(Render 进程)

浏览器的渲染进程是多线程的

  • GUI 渲染线程
    • 负责渲染页面,解析HTML,CSS,构建DOM 树,和RenderObject 树,布局和绘制等
    • 当页面需要重绘(Repaint)或者回流(Reflow)时,该线程就会执行
    • GUI 线程与JS 线程互斥,当JS 引擎执行时,GUI 线程会被挂起,GUI 更新会被保存在一个队列中,等JS 线程空闲时立即执行
  • JS 线程
    • 也称JS 内核,负责解析JS 脚本,运行代码
    • 一直等待任务队列中的任务,一个tab 页永远只有一个js 线程
    • 当JS 线程执行代码时,会将对应任务添加到事件线程中
  • 事件触发线程
    • 归属浏览器,而不是JS 线程,用来控制事件循环
    • 当事件被出发时,该线程会把事件添加到待处理队列的队尾,等待JS 引擎处理
    • 由于JS 是单线程,所以队列中事件都要排队等待JS 线程处理,当JS 引擎空闲时才会执行。
  • 定时器触发线程
    • 比如setInterval 和 setTimeout
    • 浏览器的计时器并不是由JS 线程提供的,在计时完毕后,添加到事件队列中,待JS 引擎空闲后执行,所以JS 计时器不准
    • setTimeout 中低于4ms 的时间间隔算为4ms
  • 异步http 请求线程
    • 在XMLHttpRequest 在连接后是通过浏览器新开的一个线程请求
    • 检测到状态变更时,如果有回调函数,异步线程就产生状态变更事件,将这个回调放到事件队列中,再由JS 线程执行

Browser 进程和浏览器内核(render进程)的通信过程

打开任务管理器,再打开浏览器,可以在任务管理器中看到出现了两个进程,一个是主控进程,另一个时打开tab 页的渲染进程,过程如下

  • Browser 进程收到用户请求,首先后去到页面内容(譬如通过网络下载资源),随后将该任务通过RendererHost 接口传递给Render 进程
  • Render 进程收到消息后,交给渲染进程,开始渲染
    • 渲染进程接收请求,加载并渲染网页
    • JS 线程操作DOM
    • 最后Render 进程将结果传递给Browser 进程
  • Browser 进程接收到结果并将页面绘制出来

浏览器内核中线程之间的关系

GUI 渲染线程与JS 线程互斥

  • 由于JS 线程可以操作DOM,如果在修改这些元素属性同时渲染页面,那么渲染线程前后获得的元素数据就可能不一致了
  • 为了防止渲染出现不可预期的结果,浏览器设置GUI 渲染线程与JS 线程为互斥关系,当JS 线程执行时,GUI 线程会被挂起
  • GUI 更新会被保存在一个队列中,等到JS 引擎线程空闲时立即执行

JS 阻塞页面加载

原因同上

Web Worker

由于JS 是单线程的,如果前一个任务没有结束,后面的任务只能排队,Web Worker 就是为了解决这个问题。
Web Worker的作用就是为JS 创造多线程环境,允许主线程创建Worker 线程,然后分配一些任务运行,在主线程运行的同时,Worker 线程在后台运行,互不干扰。等Worker 线程完成任务后,再把结果返回给主线程。这样的好处是一些很费时间的任务被Worker 线程承担,主线程就不会被阻塞,减少页面卡顿
Worker 线程一旦新建成功,就会始终运行,不会被主线程的活动打断,这样有利于随时响应主线程通信,但是Woker 比较耗费字眼,使用过后,应该立刻关闭

有以下几个使用注意点

  • 同源限制
    • 分配给Worker 线程运行的脚本文件,必须与主线程的脚本文件同源
  • DOM 限制
    • Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM 对象,也无法使用document、window、parent 这些对象,但是Worker 线程可以使用navigator 对象和loaction 对象
  • 通信联系
    • Worker 线程和主线程不在一个上下文环境,他们不能直接通信,必须通过消息完成
  • 脚本限制
    • Worker 线程不能执行alert 方法和confirm 方法,但是可以使用XMLHttpRequest 对象发出的Ajax 请求
  • 文件限制
    • Worker 线程无法读取本地文件,它所加载的脚本,必须来自网络

基本用法

直接点我去阮老师的博客,这里不赘述

Share Worker 与 Web Worker

  • Web Worker 只属于某个页面,不会和其他页面的Render 进程共享
    • 只属于render 进程下的一个线程
    • 所以在Chrome 在Render 进程中创建一个新的线程来运行Worker 中的JS 程序
  • Share Worker 是浏览器所有页面共享的,不能采用Web Worker 同样的方式实现
    • 一个单独的进程

浏览器渲染流程

  • 解析html 树,建立dom 树
  • 解析css 构建css 树,与dom 树合并成render 树
  • 布局render 树,负责各元素尺寸、位置的计算
  • 绘制render 树,绘制页面像素信息
  • 浏览器将各层的信息发送给GPU,GPU 会讲各层合成,显示

load 事件与 DOMContentLoaded 事件的先后

  • 当load 事件触发时,页面所有的DOM,样式表,js 脚本,图片都已经加载完成了
  • 当DOMContentLoaded 事件出发时,仅仅加载完了DOM

css 加载是否会阻塞dom 树渲染

css 是由单独的下载线程异步下载,所以不会阻塞dom 树解析,但是会阻塞render 树渲染(渲染时需要等css 加载完毕。
因为加载css 时,可能会修改dom 节点的样式,如果css 加载不阻塞render 渲染的话,当css 加载完之后render 树可能又需要重绘、回流,会造成额外性能损耗

Event Loop 和 JS 运行机制

  • JS 氛围同步任务和异步任务
  • 同步任务都在主线程上执行,形成一个==执行栈==
  • 主线程之外,事件触发线程管理者一个==任务队列==,只要异步任务运行有了结果,就在==任务队列==中推入一个事件
  • 当==执行栈==中所有同步任务执行完毕(此时JS 线程空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行

定时器

  • 由定时器线程单独控制,避免JS 线程阻塞,影响计时准确性。
  • 当使用setTimeout 或setInterval 时,就会调用定时器线程,计时完成后就会将需要执行的事件推入到事件队列中,需要等先进入队列中的任务执行完之后才会执行,所以定时器不准确

为什么setTimeout 而不是setInterval

  • setTimeOut 时间到了就会执行定时器内的任务。
  • setInterval 则是每次都精确的隔一段事件向任务队列中推入一个事件,但是事件执行时间不一定准确,可能这个事件还没执行完毕,下一个事件就来了,会导致定时器代码连续运行多次,而之间没有间隔。
  • 浏览器最小化时,setInterval 回调函数放在队列中,等窗口再打开时,一次性全部执行