跳到主要内容

Web Worker

  • Web Workers 使得一个Web应用程序可以在与主线程分离的后台线程中运行一个脚本操作
    • 这样做的好处是可以在一个单独的线程中执行费时的处理任务
    • 从而允许主(通常是UI)线程运行而不被阻塞。
  • web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能
  • 不能访问DOM
    • Worker 线程运行在沙箱环境中,无法直接访问 DOM 和 Window 对象
    • 只能通过 postMessage() 和 onmessage 进行通信

worker的限制​

  • worker不能直接访问DOM
  • 合理控制worker的数量
  • 注意worker的内存限制

Web Worker场景​

  • 性能优化
  • 复杂的计算可使用Web Worker计算,避免页面假死
  • 复杂的任务
  • 阻塞主线程任务,可通过web worker优化

业务场景:

  • 定时任务
  • 复杂的计算,如生成视频的md5

worker调试​

  • 一个网站是否启用Service Worker,可以通过开发者工具中的Application来查看:
  • 被Service Worker缓存的文件,可以在Network中看到Size项为from Service Worker:
  • 也可以在Application的Cache Storage中查看缓存的具体内容:
  • 如果是具体的断点调试,需要使用对应的线程,不再是main线程了,这也是webworker的通用调试方法:
  • Service Worker 是基于 HTTPS 的,因为Service Worker中涉及到请求拦截,所以必须使用HTTPS协议来保障安全。如果是本地调试的话,localhost是可以的

主线程和worker通信​

  • 支持 postMessage/onmessage 双向通信
    • postMessage发送消息
    • onmessage接受消息的回调事件
javascript
// main.js
const myWorker = new Worker('worker.js');
// 主线程向worker通信
myWorker.postMessage({data: [1,2,3]});
myWorker.onmessage = e => console.log('结果:', e.data);
  • worker通过meesage事件接收数据、也可以通过postMeeage向主线程通信
js
// worker.js
self.addEventListener('message', e => {
const { type } = e.data
if (type === 'welcome') self.postMessage({ type: 'print', data: 'hello world' })
})

Service Worker​

  • 一个服务器与浏览器之间的中间人角色
  • 在web worker的基础上增加了离线缓存的能力
  • 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)
  • Service Worker本质上也是浏览器缓存资源用的
  • Service worker运行在worker上下文 --> 不能访问DOM
  • 基于web worker,不会阻塞主线程,运行在后台线程的脚本
  • 出于安全考量,Service workers只能由HTTPS协议,本地调试使用localhost也是可以

Service Worker场景​

  • 离线访问:使用Service Worker可以在用户网络不畅的情况下提供离线内容,使用户仍然可以访问网站的部分内容。

  • 消息推送:Service Worker可以在后台接收和处理推送消息,并显示在网站的通知中。

  • 后台数据更新:Service Worker可以在后台下载并缓存数据,以便当用户下次访问网站时可以立即使用最新数据。

  • 资源缓存:Service Worker可以缓存静态资源,以便提高网站的加载速度和可用性

如何更新一个Service Worker​

当用户浏览你的网站,浏览器尝试在后台下载service worker的脚本文件。只要服务器上的文件和本地文件有一个字节不同,它们就被判定为需要更新

  • 更新后的service worker将开始运作,install event被重新触发
  • 在这个时间节点上,当前页面生效的依然是老版本的service worker,新的servicer worker将进入"waiting"状态。
  • 当前页面被关闭之后,老的service worker进程被杀死,新的servicer worker正式生效。 一旦新的service worker生效,它的activate事件被触发

Service Worker例子​

Service Worker的js代码里,self是一个特殊的关键字,它指向当前的Service Worker对象。

通过 self,您可以访问Service Worker对象的属性和方法,如:

  • self.addEventListener:添加事件监听器。
  • self.skipWaiting:跳过等待状态,立即激活Service Worker。
  • self.clients:返回当前控制的客户端(页面)的一个客户端对象。

Worker基本使用​

创建worker​

  • 直接指定脚本文件创建
js
const myWorker = new Worker('worker.js', options);
  • 创建 Worker:支持 URL 或 Blob
js
const worker = new Worker(URL.createObjectURL(blob));

使用worker相关问题​

webworker是否可以建立多个,创建千百个是否可以提高性能?假设是分片上传时,用两三个worker来处理?性能会更好吗​

  • 每个Worker都是一个独立的线程,会占用一定的内存和CPU资源,因此,千百个肯定是性能低下,不切实际的。合理的使用数量取决于具体的应用场景和设备性能,比如某些浏览器对同时运行的Web Worker数量有限制(例如Chrome曾经限制为每个页面最多6个Dedicated Workers)。

  • 对于分片上传,使用两三个Web Workers来处理确实可以提高性能,多个Worker可以并行处理不同的数据块,合理分配任务到多个Worker,也可以更好地利用多核CPU的优势

  • 总的来说,要根据实际需求和设备性能,选择合适的Worker数量,通常2-4个Worker是一个比较合理的范围,可以使用性能分析工具(如Chrome DevTools)监控CPU和内存使用情况,确保不会因为过多的Worker而导致性能下降,并且尽量均匀地分配任务到各个Worker,避免某些Worker过载而其他Worker闲置。

Web Worker传输大量Transferable对象时的性能问题​

  • 传输大量数据如何优化?

    • 分批传输:
      • 将大数据集分成小块进行传输,这样可以减少一次性传输的数据量,降低内存占用和传输延迟
  • worker.postMessage 的第二个参数用于指定传输的对象类型

    • 用于定义哪些对象是 Transferable 对象。Transferable 对象在传输过程中不会被复制,而是直接从发送方的上下文转移到接收方的上下文。这种方式可以显著提高性能,尤其是在处理大量数据时

    可以将数据的所有权从一个上下文转移到另一个上下文,而不需要复制数据。这可以显著减少数据传输的开销,提高性能

js
worker.postMessage(chunk.buffer, [chunk.buffer]);

Transferable 对象包括:

  • ArrayBuffer
  • MessagePort
  • ImageBitmap

当你传输这些对象时,它们的内存不会被复制,而是从一个上下文“转移”到另一个上下文,释放了源上下文对这些对象的引用

Worker的内存限制​

  • Web Worker 运行在独立的线程环境中,拥有自己的内存空间
  • 当处理大文件或大量数据时,可能会出现内存不足的问题,导致错误如“Failed to execute 'postMessage' on 'Worker': Data could not be cloned, out of memory”[
  • 在某些情况下,一次性加载大量数据到内存中会导致内存消耗过大,甚至超过浏览器分配的内存限制

解决方案:

  • 合理控制内存使用:避免一次性加载大量数据,分批处理数据以减少内存占用
  • 复用 Worker 实例:减少频繁创建和销毁 Worker 的开销[