进程与线程|浏览器内核+Web Workers实战全攻略

0 阅读7分钟

一、进程与线程

操作系统会为每个进程分配独立的空间(独立的资源),一个进程由一个或多个线程组成,同个进程下的各个线程之间共享程序的内存空间;

如上图:每个线程都拥有不同的PID(进程ID),比如 Clash for Windows是一个id29944的线程,

(一)、进程(process)

计算机已运行的程序;进程曾是``分时系统的基本运作单位;在面向线程设计的系统(如当代多数操作系统,Linux 2.6及更新的版本)中,进程本身不是基本运作单位,而是线程的容器。

(二)、线程(thread)

操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

二、单线程与多线程

如果一个进程只有一个线程,我们称之为单线程。单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。

单线程处理的优点:同步应用程序的开发比较容易,但由于需要在上一个任务完成后才能开始新的任务,所以其效率通常比多线程应用程序低

如果完成同步任务所用的时间比预计时间长,应用程序可能会不响应。针对这个问题,我们可以考虑使用多线程,即在进程中使用多个线程,这样就可以处理多个任务。

众所周知,JavaScript是单线程的。每个窗口一个JavaScript线程,既然是单线程的,在某个特定的时刻,只有特定的代码能够被执行,其它的代码会被阻塞

JS 中其实是没有线程概念的,所谓的单线程也只是相对于多线程而言。JS 的设计初衷就没有考虑这些,针对 JS 这种不具备并行任务处理的特性,我们称之为 “单线程”。

JavaScript是单线程的,但是它的宿主环境浏览器是多线程的;浏览器内核(渲染进程)中除了Javascript引擎线程之外,还含有GUI渲染线程事件触发线程定时触发器线程等。

三、浏览器内核

「浏览器最核心的部分是 “Rendering Engine”,即 “ 渲染引擎 ”,不过我们一般习惯将之称为 “ 浏览器内核 ”。」 它主要包括以下线程:

(一)、GU渲染线程

GUI 渲染线程负责渲染浏览器界面,解析 HTMLCSS,构建 DOM 树和 RenderObject 树,布局和绘制等。当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。

1. 重绘

当页面元素样式改变不影响元素在文档流中的位置时(如background-color, border-color, visibility),浏览器只会将新样式赋予元素并进行重新绘制操作;

render tree中的一部分或全部因为元素的规模尺寸、布局、隐藏等改变时,浏览器重新渲染部分DOM或全部DOM的过程;

触发回流的操作如下:

  • 页面首次渲染
  • 浏览器窗口大小发生变化
  • 元素尺寸或位置发生变化
  • 元素内容变化(文字数量或图片大小等)
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(:hover
  • 查询某些属性或调用某些方法

3. 性能影像

回流比重绘的代价要更高;

即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流

现代浏览器会对频繁的回流或重绘操作进行优化: 浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

4. 如何避免

  • CSS

    • 避免使用table布局
    • 尽可能在DOM树的最末端改变class
    • 避免设置多层内联样式
    • 将动画效果应用到position属性为absolute或fixed的元素上
    • 避免使用CSS表达式(例如:calc())
  • JavaScript

    • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性
    • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
    • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
    • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
    • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流

(二)、JavaScript 引擎线程

JavaScript 引擎线程负责解析 JavaScript 脚本并运行相关代码。 JavaScript 引擎一直等待着任务队列中任务的到来,然后进行处理,一个Tab页(Renderer 进程)中无论什么时候都只有一个 JavaScript 线程在运行 JavaScript程序。

Tips

GUI 渲染线程与 JavaScript 引擎线程是互斥的,所以如果 JavaScript 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染被阻塞。

(三)、事件触发线程

当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于 JavaScript 引擎是单线程的,所有这些事件都得排队等待 JavaScript 引擎处理。

(四)、定时触发器线程

浏览器定时计数器并不是由 JavaScript 引擎计数的,这是因为 JavaScript 引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确,所以通过单独线程来计时并触发定时是更为合理的方案。我们日常开发中常用的 setIntervalsetTimeout 就在该线程中。

(五)、Http异步请求线程

XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。

由于 JavaScript 引擎与 GUI 渲染线程是互斥的,如果 JavaScript引擎执行了一些计算密集型或高延迟的任务,那么会导致 GUI 渲染线程被阻塞或拖慢。那么如何解决这个问题呢? Web Workers

四、Web Workers

web worker允许一段Javascript程序运行在主线程之外的另一个线程中、Web Worker的作用,就是为了JavaScript创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行

(一)、web worker Api

web worker的环境与window不同、无法在web Worker线程中直接操作Dom元素,还有些许window对象的某些方法和属性;

Web Worker所支持的API:

  • Cache
  • CustomEvent
  • Fetch
  • Promise
  • FileReader
  • IndexedDB
  • WebSocker
  • XMLHttpRequest

(二)、主线程与 Web Worker之间的通信

主线程和 Worker 线程相互之间使用postMessage()方法来发送信息,并且通过 onmessage这个事件处理器来接收信息。数据的交互方式为传递副本,而不是直接共享数据。主线程与 Worker 线程的交互方式如下图所示:

vue项目中如下:

  1. 安装 worker-loader

npm i worker-loader -S -D

  1. 配置worker-loader

vue.config.jswebpack中配置如下:

module.exports = {
  configureWebpack: {
    module: {
      rules: [
        // web-worker 的配置
        {
          test: /.worker.js$/,
          use: {
            loader: 'worker-loader',
            // 允许将内联的 web worker 作为 BLOB
            options: { inline: 'no-fallback' }
          }
        },
        // js的配置
        {
          // 用来匹配文件名称的
          test: /.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }
      ]
    }
  }
}
  1. 定义my.worker.js文件,文件夹后缀命明如配置文件所写,为的解析;
// my.worker.js
function fib(n) {
  if (n < 3) {
    return 1
  } else {
    return n * 5
  }
}
// 接收数据
onmessage = function (event) {
  console.log('分线程', event.data, typeof event.data)
  const res = fib(event.data || 0)
  // const res=1200
  // 传输数据
  postMessage(res)
}
  1. vue文件中启用web-worker
import Worker from './test.worker'
export default {
  name: 'home',
  data() {
    return {
      worker: null
    }
  },
  mounted() {
    const a = 1
    this.worker = new Worker()
    console.log(this.worker)
    this.worker.postMessage(a)
    // 注册监听函数,接收子线程消息
    this.worker.onmessage = (event) => {
      console.log(111111)
      console.log(event.data)
    }
  }
}
</script>

web-workerpostMessage传输的值必须是可复制的值,并且只能传一个实参