Web Worker

596 阅读4分钟
进程与线程的区别
进程

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

线程

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

单线程和多线程
  1. 单线程:同步顺序执行,其效率比多线程低

  2. 多线程:多条线路执行

浏览器内核

82AA353E-59C9-4DC4-9EE4-70499B5BA767.png

Web Worker
定义

允许一段 JavaScript 程序运行在主线程之外的另外一个线程中。Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行,等到 Worker 线程完成计算任务,再把结果返回给主线程。

优点

可以在独立线程中处理一些计算密集型高延迟的任务,从而允许主线程(通常是 UI 线程)不会因此被阻塞或拖慢

限制与能力

无法在 worker 线程中操纵 DOM 元素,或使用 window 对象中的某些方法和属性

主线程和Web Worker之间通信

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

6E6C4EC0-EDBC-4B3E-A442-F3CE27B1513F.png

Web Worker的分类

定义: 指标准worker仅在单一脚本中被使用

每个 Web Worker 都可以创建自己的子 Worker,这允许我们将任务分散到多个线程

注意: 在主线程中使用时,onmessage和postMessage() 必须挂在worker对象上,而在worker中使用时不用这样做。原因是,在worker内部,worker是有效的全局作用域

866A9A86-44C4-4465-9551-C0325EE0279B.png

  • 方法

  • onmessage

  • postMessage

  • terminate

  • self.close

  • onerror

  • importScripts 引入脚本

  • 其他

  • CPU核心数= window.navigator.hardwareConcurrency ,用于计算最大并发线程数


// main.js ********************* 主线程

const worker = new Worker('worker.js')

// 监听

worker.onmessage = (e) => { console.log(e.data) }

// 通信

worker.postMessage('I am main')

// 终止 worker进程

worker.terminate()


// worker.js ********************* 子线程

// 引入脚本

importScripts('foo.js');

// 在 new Worker时,创建了全局作用域 globalthis -> worker

// self 属于全局属性

self.onmessage = (e) => { ... }

self.postMessage('I am Worker')

// 自己关闭进程

self.close()

subWorker()

// 线程的子线程

function subWorker () {

    const worker = new Worker('worker.sub.js')

    worker.onmessage = (e) => { ... }

    worker.postMessage('I am worker.sub')

}


// worker.sub.js ********************* 子线程的线程

self.onmessage = (e) => { ... }

self.postMessage('I am Worker')

定义:共享worker可以同时被多个脚本使用(多个窗口,共享一个数据池)

区别:Dedicated Worker,共享worker通信必须通过端口对象——一个确切的打开的端口供脚本与worker通信(在专用worker中这一部分是隐式进行的)

  • 方法

  • start

  • post.onmessage

  • post.postMessage

  • self.onconnect


// main.js *********** 主线程

const shareWorker = new SharedWorker('worker.share.js')

shareWorker.start()

shareWorker.post.onmessage = (e) => { ... }

shareWorker.post.postMesaage('I am main (Share)')


// worker.share.js ****** 共享子线程

let a = 0

self.onconnect = function (e) {

var port = e.ports[0]

port.onmessage = function () {

        console.log('share', e, a)

        port.postMessage(a++)

    }

}

  1. Shared Worker的调试,在实际项目开发过程中,若需要调试 Shared Workers 中的脚本,可以通过 chrome://inspect 来进行调试,具体步骤如下图所示:

C4347F5F-4AF1-46D6-A7EF-64B697332C8E.png

通过转让所有权(转让对象)来传递数据

Google Chrome 17 与 Firefox 18 包含另一种性能更高的方法来将特定类型的对象(可转让对象) 传递给一个 worker/从 worker 传回 。可转让对象从一个上下文转移到另一个上下文而不会经过任何拷贝操作。这意味着当传递大数据时会获得极大的性能提升。如果你从 C/C++ 世界来,那么把它想象成按照引用传递。然而与按照引用传递不同的是,一旦对象转让,那么它在原来上下文的那个版本将不复存在。该对象的所有权被转让到新的上下文内。例如,当你将一个 ArrayBuffer 对象从主应用转让到 Worker 中,原始的 ArrayBuffer 被清除并且无法使用。它包含的内容会(完整无差的)传递给 Worker 上下文。


// Create a 32MB "file" and fill it.

var uInt8Array = new Uint8Array(1024*1024*32); // 32MB

for (var i = 0; i < uInt8Array .length; ++i) {

    uInt8Array[i] = i;

}

worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

参考文章
  1. 你不知道的 Web Workers (上)

  2. Transferable Objects: Lightning Fast!