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接受消息的回调事件
// 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向主线程通信
// 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
- 直接指定脚本文件创建
const myWorker = new Worker('worker.js', options);
- 创建 Worker:支持 URL 或 Blob
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 对象在传输过程中不会被复制,而是直接从发送方的上下文转移到接收方的上下文。这种方式可以显著提高性能,尤其是在处理大量数据时
可以将数据的所有权从一个上下文转移到另一个上下文,而不需要复制数据。这可以显著减少数据传输的开销,提高性能
worker.postMessage(chunk.buffer, [chunk.buffer]);
Transferable 对象包括:
ArrayBufferMessagePortImageBitmap
当你传输这些对象时,它们的内存不会被复制,而是从一个上下文“转移”到另一个上下文,释放了源上下文对这些对象的引用
Worker的内存限制
- Web Worker 运行在独立的线程环境中,拥有自己的内存空间
- 当处理大文件或大量数据时,可能会出现内存不足的问题,导致错误如“Failed to execute 'postMessage' on 'Worker': Data could not be cloned, out of memory”[
- 在某些情况下,一次性加载大量数据到内存中会导致内存消耗过大,甚至超过浏览器分配的内存限制
解决方案:
- 合理控制内存使用:避免一次性加载大量数据,分批处理数据以减少内存占用
- 复用 Worker 实例:减少频繁创建和销毁 Worker 的开销[