likes
comments
collection
share

探索 Web Worker

作者站长头像
站长
· 阅读数 57

为什么要用web worker

在前端页面中,如果突然出现一个大量计算的场景,可能会导致页面无法交互。这种情况通常发生在执行复杂的算法、处理大量数据或进行大规模计算的情况下。

当页面遇到大量计算时,主线程被占用,无法及时响应用户的交互操作,导致页面出现卡顿、无响应的情况。用户可能会感到失望和不满,因为他们无法流畅地与页面进行交互,无法及时看到更新的内容或响应的反馈。

为了解决这个问题,就可以使用 Web Worker

什么是web worker

Web Worker 是 HTML5 提供的一种浏览器内置的 JavaScript 线程,它运行在后台,独立于主线程(也称为 UI 线程)。

Web Worker 可以执行耗时的任务,而且不会阻塞主线程的运行。通过将耗时的计算任务委托给 Web Worker,主线程可以更专注于处理用户交互和响应,从而提高应用的性能和流畅度。Web Worker 还可以利用多核 CPU 的优势,加速任务的执行速度。

使用web worker的注意事项

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

Web Worker 有以下几个使用注意点。

(1)同源限制 分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

(2)DOM 限制 Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。但是,Worker 线程可以navigator对象和location对象。

(3)通信联系 Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

(4)脚本限制 Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

(5)文件限制 Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

专用worker的创建和通信

一个专用 worker 仅能被生成它的脚本所使用,就是专门给一个窗口或者标签页使用的。

创建 Web Worker 实例

在主线程中,通过 new Worker() 构造函数创建一个 Web Worker 实例,并指定 Worker 脚本文件的路径。并为 Web Worker 实例添加消息和错误事件的监听器,以处理来自 Worker 的消息和错误信息。 使用postMessageonmessage方法的向worker发送或者接收数据

  // 在主线程中创建 Web Worker 实例
  const worker = new Worker('worker.js');
  
  // 监听来自 Worker 的消息
  worker.onmessage = function (event) {
    const message = event.data;
    // 处理消息
    console.log("message:",message)
  };
  
  // 监听 Worker 的错误信息
  worker.onerror = function (error) {
    console.error('Worker error:', error);
  };
  
  // 发送消息给 Worker
  worker.postMessage('Message from Main Thread');
  
  

创建 Worker 脚本文件

首先,创建一个独立的 JavaScript 文件,作为 Worker 的脚本文件。该文件将包含要在后台线程中执行的代码逻辑。

  // worker.js
  
  // self代表子线程自身,即子线程的全局对象,使用onmessage方法监控主程序的消息
  self.onmessage = function (event) {
    // 获取主应用发送的消息
    var message = event.data;
    
    // 处理接收到的消息
    console.log('Received message:', message);
  
    // 发送消息给主线程
    self.postMessage('Message from Worker');
    
  };
  

关闭worker的方法

如果你需要从主线程中立刻终止一个运行中的 worker,可以调用 worker 的 terminate方法:

  // 主线程中
  worker.terminate();

也可以在worker线程中使用 self.close()关闭worker线程

  // Worker 线程中
  self.close();

在worker中引入脚本

Worker 线程能够访问一个全局函数 importScripts() 来引入脚本,该函数接受 0 个或者多个 URI 作为参数来引入资源;

  importScripts();                        /* 什么都不引入 */
  importScripts("foo.js");                /* 只引入 "foo.js" */
  importScripts("foo.js", "bar.js");      /* 引入两个脚本 */

举个使用场景的例子-大型数据排序

当涉及到大型数据集的排序、图像处理或计算密集型任务时,Web Worker 可以发挥其多线程的优势,提升前端应用的性能和响应能力。下面以排序大型数组为例,演示如何使用 Web Worker 进行并行排序:

  // index.js
  
  // 创建 Web Worker 实例
  const worker = new Worker('worker.js');
  
  // 生成一个大型数组
  const array = Array.from({ length: 1000000 }, () => Math.random());
  
  // 发送数据给 Web Worker
  worker.postMessage(array);
  
  // 接收 Web Worker 返回的排序结果
  worker.onmessage =function(event) {
    const sortedArray = event.data;
    console.log('Sorted Array:', sortedArray);
  };
  
  //worker.js
  
  // 监听消息事件
  self.addEventListener('message', function(event) {
    const array = event.data;
  
    // 使用排序算法对数组进行排序
    const sortedArray = parallelSort(array);
  
    // 发送排序结果给主应用
    self.postMessage(sortedArray);
  });
  
  // 并行排序算法
  function parallelSort(array) {
    // 实现排序的逻辑
    return array.sort((a,b)=>a-b);
  }
  

在上述示例中,主应用生成了一个包含1000000个随机数的大型数组,并将该数组发送给 Web Worker。Web Worker 接收到数组后,使用并行排序算法对数组进行排序,并将排序结果发送回给主应用。主应用监听 Web Worker 返回的消息,并在控制台打印排序后的数组。

通过使用 Web Worker,在进行大型数据集的排序等计算密集型任务时,可以将任务分发到多个线程中执行,提高排序速度和响应能力,避免阻塞主线程影响用户界面的交互和响应。

共享worker

共享线程(SharedWorker)是 Web Worker 的一种特殊类型,它可以被多个窗口或标签页共享。与普通的 Web Worker 只能与创建它的窗口或标签页通信不同,共享线程可以与多个窗口或标签页进行通信,从而实现跨窗口或标签页的数据共享和协同处理。

创建共享worker实例

主线程使用 new SharedWorker('worker.js') 创建一个共享线程实例,并通过 sharedWorker.port 来发送和接收消息。

  // 创建共享线程实例
  const sharedWorker = new SharedWorker('worker.js');
  
  // 监听共享线程的消息
  sharedWorker.port.onmessage = function(event) {
    const message = event.data;
    console.log('Received message from shared worker:', message);
  };
  
  // 向共享线程发送消息
  sharedWorker.port.postMessage('Hello from main thread!');
  

共享线程使用 self.onconnect 事件来监听主线程的连接请求,并使用 port.onmessage 监听主线程发送的消息。共享线程也可以通过 port.postMessage() 向主线程发送消息。

  // 监听主线程的消息
  self.onconnect = function(event) {
    const port = event.ports[0];
  
    // 监听消息
    port.onmessage = function(event) {
      const message = event.data;
      console.log('Received message from main thread:', message);
  
      // 向主线程发送消息
      port.postMessage('Hello from shared worker!');
    };
  
  };
  

其他类型的worker

除了专用和共享的 web worker,还有一些其他类型的 worker:

ServiceWorkers 基本上是作为代理服务器,位于 web 应用程序、浏览器和网络(如果可用)之间。它们的目的是(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动并更新驻留在服务器上的资源。它们还将允许访问推送通知和后台同步 API。

Audio Worklet 提供了在 worklet(轻量级的 web worker)上下文中直接完成脚本化音频处理的可能性。

总结

Web Worker 的性能优势和注意事项主要涉及线程间通信的开销、数据同步和错误处理。下面对这些方面进行探讨

性能优势:

  • 并行计算:Web Worker 可以在后台运行,与主线程并行执行任务,充分利用多核处理器的优势,提高计算能力和任务处理速度。
  • 解放主线程:将复杂、耗时的任务交给 Web Worker 处理,可以减轻主线程的负担,避免阻塞页面渲染和用户交互。
  • 提升响应能力:将计算密集型任务委托给 Web Worker,主线程可以更快地响应用户输入和交互,提高页面的流畅性和响应速度。

注意事项:

  • 线程间通信开销:Web Worker 之间的通信开销相对较大,因此应避免频繁的通信。尽量减少传递大量数据或频繁发送消息,以降低通信开销。
  • 数据同步:Web Worker 和主线程之间的数据是相互隔离的,无法直接共享。需要通过消息传递的方式进行数据同步。在传递数据时要注意序列化和反序列化的开销。
  • 错误处理:Web Worker 的错误不会直接抛出到主线程,而是需要通过监听 error 事件或使用 onerror 处理函数来捕获错误。在开发过程中要充分考虑错误处理和调试机制。

综上所述,Web Worker 在处理大型数据集、计算密集型任务message等方面具有明显的性能优势。但在使用时需要注意线程间通信开销、数据同步和错误处理等问题,以充分发挥其优势并确保应用的稳定性和性能。

转载自:https://juejin.cn/post/7246672925666148409
评论
请登录