浏览器进程与线程

进程与线程之间的关系

每个进程拥有完全不同的虚拟地址空间,一个进程包含一到多个线程,一个进程内的所有线程共享同一内存空间,线程与线程之间是没有隔离的,线程之间共同完成进程分配下来的任务,比如:

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

一个程序至少拥有一个进程,一个进程至少拥有一个线程。进程是资源分配的基本单位,线程是资源调度的基本单位。

进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

如果你把进程看作是工厂,而线程就是工人,上面的话就很好理解了。

线程崩溃是否会造成进程崩溃?

由于一个进程内的所有线程共享同一内存空间,线程与线程之间是没有隔离的,因此线程崩溃的确会造成进程崩溃。当一个线程向非法地址读取或者写入,无法确认这个操作是否会影响同一进程中的其它线程,所以只能是整个进程一起崩溃。

浏览器中的进程

浏览器是多进程的,它包含有:

  • 主进程
    • 负责浏览器界面显示,与用户交互。如前进,后退等。
    • 负责各个页面的管理,创建和销毁其他进程。
    • 将渲染(Renderer)进程得到的内存中的 Bitmap(位图),绘制到用户界面上。
  • 第三方插件进程,浏览器会为每一个插件开启一个进程,只有使用该插件的时候才会创建相应的进程。
  • GPU 进程,用于显卡硬件加速,在你用浏览器观看高清视频或渲染 3D 图形的会开启,最多只有一个进程。
  • 渲染进程,每开启一个标签页就会多一个进程,负责页面渲染、脚本执行和时间处理等。
  • 网络进程,主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。

有时会有其他情况:

  • 如果页面中有 iframe,iframe 也会运行在单独的进程中。
  • 如果两个页面属于同一个站点,并且 B 页面是从 A 页面中打开的,那么他们会共用一个渲染进程。

Notice

Chrome 的默认策略是,每个标签对应一个渲染进程,但如果从一个页面通过 window.open 而不是 url 打开了新页面,新页面与原页面属于同一站点,那么新页面会复用父页面的渲染进程,官方把这个默认策略叫 process-per-site-instance. 在这种情况下,一个页面崩溃,会导致同一站点的页面同时崩溃,因为他们使用了同一个渲染进程。另外,多个新建标签页也会合并为一个渲染进程,但如果在新标签页进行搜索之后,就会独立出来一个渲染进程。

渲染进程

渲染进程包含 6 个线程,如下:

  1. GUI 渲染线程
    • 负责渲染浏览器界面,包括解析 HTML、CSS、构建 DOM 树、Render 树、布局与绘制等。
    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。
  2. JS 引擎线程
    • JS 内核,也称 JS 引擎,负责处理执行 javascript 脚本。
    • 等待任务队列的任务的到来,然后加以处理,浏览器无论什么时候都只有一个 JS 引擎在运行 JS 程序。

Notice

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

  1. 事件触发线程
    • 用来控制事件循环,该线程归浏览器而不是 JS 引擎所控制。
    • 当 JS 引擎异步代码时,会将对应的异步任务添加到事件线程中等待处理。
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到事件队列的队尾,等待 JS 引擎的处理。
  2. 定时器线程
    • 当执行 setTimeoutsetInterval 时就会开始计时,当计时完成后添加到事件队列,等待 JS 引擎执行。

Notice

定时器线程是独立出来的一个线程,因为如果由 JS 引擎计时,当处于堵塞状态时计时将会出现差错。但计时器中的事件仍然可能会因为其他任务的阻塞延迟执行。

  1. 异步 http 请求线程
    • 在 XMLHttpRequest 连接后是通过浏览器新开一个线程请求,在检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由 JavaScript 引擎执行。
  2. IO 线程
    • 用来和其他进程进行通信,例如: 网络进程下载完成以后就会将信息发送给 IO 线程,通过 IPC 通信。当用户点击页面按钮,浏览器主线程就会把这个信息通过 IPC 发送给 IO 线程。IO 线程会将这个时间包装成任务添加到消息队列里面,供事件循环机制不断处理。

单线程的 JS

实际上,所有涉及到 UI 处理的都必须是单线程,如在 Android 中,所有处理 UI 的代码必须在主线程中执行,这是为了防止其他子线程中的代码操作 UI 造成冲突。JS 现在虽然可以使用 web worker 开启新的子线程,但只能做计算,不能操作 UI。

在创建 Worker 时, JS 引擎会向浏览器申请新开一个线程,这样的话在 Worker 线程中做运算就不会影响到主线程了。

WebWorker 与 SharedWorker 的区别?

首先,WebWorker 只属于某个页面的渲染进程中,不与其他页面的渲染进程共享。而 SharedWorker 在浏览器中是所有页面共享的(当然前提是这些页面必须是同源的(相同的协议、host 以及端口)),它不隶属于某个渲染进程,浏览器会为其单独创建一个进程来运行 JS 代码。

GUI 线程与 JS 线程

GUI 线程与 JS 线程是互斥的,页面进行渲染时,不能执行 JS 代码;JS 代码的执行会阻塞页面渲染。同样是为了避免 JS 中操作 UI 带来的结果不一致。

参考文章