blog icon indicating copy to clipboard operation
blog copied to clipboard

浏览器系列 - Javascript单线程与浏览器多进程

Open HXWfromDJTU opened this issue 4 years ago • 0 comments

前言

  • 进程是CPU资源分配的最小单位,是能拥有资源和独立运行的最小单位。

  • 线程是CPU调度的最小单位,线程是建立在进程的基础上的一次程序运行,一个进程可以有多个线程。

  • 不同进程之间也可以通信,不过代价比较大。

常说的单线程多线程都是指在一个进程内的单和多,也就是说,敲黑板...这些多线程单线程都必须属于一个进程才行。

浏览器多进程

浏览器是多进程的,新增一个tab页面至少新增一个进程,然后操作系统会给每个进程都分配CPU和内存,一个浏览器可以有多个tab页面

那么整体来说,OS就给一个浏览器开启了多个进程,如下图

相比较单进程浏览器,多进程浏览器有以下优势:

  • 避免单个 tab 页面阻塞运行,甚至 crash 的话也整个浏览器也都跟着崩溃
  • 避免第三方插件引起的 crash 导致整个浏览器崩溃
  • 多进程充分利用多核 CPU 优势
  • 方便使用沙盒模式隔离插件等进程,提高浏览器稳定性

简而言之,就是几大功能模块都使用单独的进程去运作,各个功能之间尽量不要相互影响。

渲染线程

我们作为 FEer 最关注的其实是渲染进程,因为它包括了我们开发、优化需要用到的

页面渲染 :arrow_right: JS执行 :arrow_right: 事件的循环 :arrow_right:

首先我们牢记一点,浏览器的渲染进程多线程的。

浏览器的渲染进程包括了以下线程:

GUI渲染线程

  • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制。
  • 当界面需要Repaint或者reflow的时候,该线程就会执行

JS引擎线程

  • 也称之为JS内核,负责处理Javascript/脚本程序
  • JS引擎负责解析Javascript脚本,运行代码。
  • JS引擎一直等待着任务队列中的任务到来,然后加以处理,一个Tab页中无论什么时候都只有一个JS线程运行JS程序。

注意

GUI渲染线程与JS线程引擎是互斥的,当JS引擎执行的时候,GUI线程会被挂起,相当于被冻结了,GUI更新会被保存在一个队列中,等到JS引擎空闲的时候立即被执行。

所以 Javascript 脚本执行的时间过长,这样就会造成渲染不连贯,导致页面渲染加载阻塞。

事件触发线程

  • 事件触发线程,注意要区别于JS引擎,用来控制事件循环。
  • 当引擎执行到了宏任务和微任务(setTimeout 用户点击 nextClick)的时候,JS引擎就会吧这些任务交给事件触发线程中。
  • 当对应的事件符合条件被触发时,该线程会把事件添加到待处理的队列的队尾,等待JS引擎的处理。
  • 注意,由于JS单线程的机制,所以这些等待处理的异步操作会等到JS引擎空闲的时候才会去执行。(eventLoop)

定时器触发线程

  • 专门用于setIntervalsetTimeout所在的线程
  • 浏览器开启定时器后,倒计时并不是由Javascript引擎自己计数的,因为单线程的原因,JS引擎本身做好自己的代码运行工作就好,还要来倒计时,真实分身乏术啊。
  • 所以定时器触发线程的作用就是用来帮忙计数的,计数完成后,将回调函数推入事件队列中,等待主线程执行。

异步http请求线程

  • XMLHttpRequest在两级后通过浏览器新开一个线程进行发起请求。
  • 将检测到状态变动的时候,我们常常会设置一些成功或者失败的回调函数,状态发生变化之后,http请求线程就会将这些回调代码交给 Javascript 引擎执行。

从细微解释现象

网页渲染过程

用户打开了一个浏览器,会默认打开一个空的tab页面,此时与浏览器相关的会有:浏览器主进程这个tab的渲染进程

  1. 浏览器输入 url ,浏览器主进程接管,开一个下载进程。

  2. 进行http请求,等待响应,获取内容。

  3. 随后将内容,通过 RenderHost 传递给 Renderer 进程,浏览器渲染渲染进程开始工作。

  4. 解析html建立DOM

  5. 解析css ,将CSS代码解析成属性的数据及结构,然后结合DOM合并成render

  6. 布局 render 树,重绘和重排,负责各元素尺寸、位置的计算。

  7. 浏览器会将各层的信息发送给 GPUGPU 会将各层合成显示在屏幕上。

  8. 最后,渲染进程将渲染结果传递回给,浏览器主进程,主进程再将结果绘制出来。

UI渲染和JS代码运行相互阻塞吗?

面试中常常会问到,浏览器再加载JS代码的时候,页面UI渲染是否停止?重绘重排呢?

今天,我们知道了GUI渲染线程与JS引擎线程是互斥的,因为Javascript线程中可以操作DOM元素的,若果在JS修改这些元素的属性的同时渲染界面,那么渲染现成前后获得的元素数据可能就不一样了。

因此为了防止渲染会出现不同的结果,那么浏览器内核就将UI渲染(包括重绘和重排版)与JS引擎执行JS代码设置为互斥的关系,GUI的更新一般会被暂时的存放到一个异步的队列中,等待JS引擎空闲的时候再被执行(这里应该理解为GUI变动被渲染引擎执行)。

浏览器 渲染引擎 JS引擎
firefox gecko monkey
IE Trident Chakra
edge edge Chakra
Opera Presto Carakan
chrome webkit V8
safari webkit SquirrelFish

从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。 所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

css加载是否阻塞DOM树渲染

css是由http下载线程单独的下载线程异步下载,所以不会阻塞DOM树的解析。

css解析,会阻塞render树进行渲染

  • 因为render树是由DOM树解析和CSS解析后一同形成的。
  • 因为加载css的时候,可能会修改下面DOM节点的样式,若不阻塞render树渲染,那么render树可能又得重新回流了。

WebWorker 与 SharedWorker

单页面的WebWorker

在红宝书学习webworker的时候,记住了一句

webworker是一个独立于widnow外的一个独立作用域,webworker仅仅拥有window对象上的部分属性和方法,特别是没有操作DOM的能力。而且只能透过postMessage这种特殊的方式与主JS引擎线程进行传递信息

以上是回忆...不是原文:joy:

浏览器创建worker,分配一个单独子线程,受控于主线程,而且不能够操作DOM,自然也不会与GUI渲染线程相互阻塞了。所以我们一般会使用webworker去做一些大量耗时的计算,然后通过postMessage()方法传回拥有window对象的JS引擎线程中去。并且悄悄补一句...postMessage是``异步任务,属于宏任务`,自然更不存在阻塞的问题了..

多页面共享 SharedWorker

要说WebWorker是服务于一个渲染进程内的多个线程...帮助他们(JS引擎线程,GUI渲染线程)减缓压力。 那么SharedWorker就是一个浏览器内,多个tab页的多个进程的帮手,要实现服务于多个进程之间,那么SharedWorker本身也是一个依托于一个进程。 每开启一个浏览器,则多个页面会共享这个SharedWorker对象。

参考文章

[1] JS多线程 - 掘金 by dailc

[2] V8与字节码

[3] chrome浏览器页面渲染工作原理浅析

HXWfromDJTU avatar Nov 08 '20 15:11 HXWfromDJTU