诗林 发表于 2024-11-29 19:20:12

Web Worker 入门:让前端应用多线程化

引出:
   作为前端切图仔,在之前的工作中一直都是写后台,没机会用到web Worker,传统的性能优化web Worker用到的场景也很少,毕竟大量的数据计算一样平常直接给后端去做就行,轮不到前端来思量(没遇到雷同的场景),那为什么要学习web Worker呢?如今的部门是做AIGC相关的,就在想前端能不能也实现一些ai模型检测功能,扩展开之后发现了前端可以使用TensorFlow.js ,WebDNN等实现一些简单的模型算法,模型的运行就必要单独一个线程来跑,不能影响主线程,以是就使用到了web worker.
1、web worker概述

什么是web worker?
Web Workers 是 HTML5 提供的一种在后台线程中运行脚本的机制。它允许 JavaScript 在主线程之外创建一个或多个独立的后台线程,这些线程可以执行脚本而不会阻塞主线程。
主线程和 Web Workers 线程之间通过消息传递来进行通信。消息传递是基于事件机制的,通过postMessage()方法发送消息,通过onmessage事件来接收消息。
web worker使用场景有哪些?
1、数据处理与计算
数据处理:Excel类应用的数据处理,日志分析,数据统计和聚合,CSV/JSON大文件处理
复杂计算:科学计算,3D渲染计算,金融数据分析,图形处理算法
2、媒体处理
图像处理: 图片批量处理,实时图像滤镜,图片压缩,图像特效处理
音视频处理:音视频转码,音频分析,视频帧提取,实时音频处理
3、前端AI和呆板学习
图像分类 ,自然语言处理,推荐系统,非常检测
4、加密和安全
文件加密,暗码哈希,数字署名,安全通信
web worker使用注意点:
不是所有的任务都得当使用worker
必要思量任务的计算密度
评估数据传输资源
内存管理:实时释放不必要的资源,克制内存泄漏。控制worker数量
2、实现一个最简单的web worker

```vue // 主线程代码 main.js const worker = new Worker('worker.js'); // 发送消息到 Worker
worker.postMessage({ type: ‘START’, data: largeArray });
// 接收 Worker 消息
worker.onmessage = function(e) {
console.log(‘Worker 返回结果:’, e.data);
};

```vue
// worker.jsworker线程代码
self.onmessage = function(e) {
const { type, data } = e.data;
if (type === 'START') {
    const result = processData(data);
    self.postMessage(result);
}
};
web worker是通过构造函数的形式来实现,通过new 一个 Worker函数,就创建了worker。
构造函数Worker()接受两个参数
const worker = new Worker(path, options);
参数解释path有效的js脚本的地点,必须遵守同源策略。无效的js地点大概违背同源策略,会抛出<font style="color:rgb(0, 0, 0);">SECURITY_ERR </font>范例错误options.type可选,用以指定 worker 范例。该值可以是 <font style="color:rgb(0, 0, 0);">classic</font> 或 <font style="color:rgb(0, 0, 0);">module</font>。 如未指定,将使用默认值 <font style="color:rgb(0, 0, 0);">classic</font>options.credentials可选,用以指定 worker 凭证。该值可以是 <font style="color:rgb(0, 0, 0);">omit</font>, <font style="color:rgb(0, 0, 0);">same-origin</font>,或 <font style="color:rgb(0, 0, 0);">include</font>。如果未指定,大概 type 是 <font style="color:rgb(0, 0, 0);">classic</font>,将使用默认值 <font style="color:rgb(0, 0, 0);">omit</font> (不要求凭证)options.name可选,在 DedicatedWorkerGlobalScope 的情况下,用来表示 worker 的 scope 的一个 DOMString 值,主要用于调试目标 上面的代码中worker 有两个方法,分别是postMessage()发送消息和onmessage()接收消息,这在主线程和worker线程中都是通用的。
其中必要注意的是worker线程中self是一个关键字,代表着当前woker的全局作用域(雷同于欣赏器主线程中的window),可以通过他调取web worker中的属性和方法。
主线程和worker线程都有 接受消息,发送消息的功能。
我们也能用addEventListener事件进行监听
// 创建一个Web Worker对象
    const worker = new Worker('worker.js');

    // 监听Web Worker的message事件,接收来自Web Worker的消息
    worker.addEventListener('message', function (e) {
      console.log('主线程接收到Web Worker的消息:', e.data);
    });

    // 监听Web Worker的error事件,当Web Worker出现错误时触发
    worker.addEventListener('error', function (e) {
      console.log('Web Worker出错啦:', e.message);
    });

    // 向Web Worker发送消息
    worker.postMessage('你好,Web Worker!这是主线程发送的消息');
// 监听主线程发送过来的消息事件
self.addEventListener('message', function (e) {
console.log('Web Worker接收到主线程的消息:', e.data);

// 模拟进行一些处理
const processedData = '我已收到你的消息:' + e.data + ',这是Web Worker的回复';

// 向主线程发送处理后的消息
self.postMessage(processedData);

// 可以在这里添加更多逻辑,比如根据接收到的不同消息执行不同任务等

});

// 监听Web Worker自身的error事件,当在Web Worker内部运行出现错误时触发
self.addEventListener('error', function (e) {
console.log('Web Worker内部出错啦:', e.message);
});
当我们把任务派发到worker线程后,worker线程在不影响主线程的情况下执行任务,执行完成之后再把结果发送到主线程。
通过上述的代码我们可以看到web worker的基本性质:
1、线程隔离:worker在独立线程中运行,与主线程完全隔离,互不影响。
2、消息通信:主线程和worker线程通过消息进行线程中的通信,
2、状态隔离:主线程和worker都有自己的作用域和状态。
线程创建后就涉及到销毁:
// main.js(主线程)
myWorker.terminate(); // 关闭worker

// worker.js(worker线程)
self.close(); // 直接执行close方法就ok了
复制代码

在主线程和worker线程都可以关闭worker,那么有啥区别呢?
主线程关闭:
web worker线程会被立刻终止,比力强硬,worker线程中的任务强制中断,不会等待当前任务完成
worker线程关闭:
web worker线程不会被立刻终止,而是在执行完当前任务之后,调用 <font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">postMessage()</font> 方法发送信息,完成最后的任务才终止,比力优雅。
在开辟中可以根据具体需求来使用
3、worker池的实现

在实际开辟中 我们会遇到必要开启许多个web worker线程的场景,这时候就要用到web worker池,先上代码: // Web Workers 线程池管理类
// 用于创建和管理一组 Web Worker,实现任务的并行处理
class WorkerPool {
    /**
   * 构造函数
   * @param {string} workerScript - Worker 脚本的路径
   * @param {number} poolSize - 线程池大小,默认为CPU核心数
   */
    constructor(workerScript, poolSize = navigator.hardwareConcurrency) {
      // 存储所有 worker 实例的数组
      this.workers = [];
      // 等待获取 worker 的任务队列
      this.queue = [];
      // 当前正在使用的 worker 集合
      this.activeWorkers = new Set();

      // 初始化 Worker 池 - 创建指定数量的 worker 实例
      for (let i = 0; i < poolSize; i++) {
      const worker = new Worker(workerScript);
      this.workers.push(worker);
      }
    }

    /**
   * 执行任务
   * @param {*} task - 要执行的任务数据
   * @returns {Promise} 返回任务执行的结果
   */
    async execute(task) {
      // 获取一个可用的 worker
      const worker = await this.getAvailableWorker();
      
      return new Promise((resolve, reject) => {
      // 设置 worker 完成任务的回调
      worker.onmessage = (e) => {
          this.releaseWorker(worker); // 释放 worker
          resolve(e.data); // 返回处理结果
      };

      // 设置 worker 错误处理
      worker.onerror = (error) => {
          this.releaseWorker(worker); // 释放 worker
          reject(error); // 返回错误信息
      };

      // 向 worker 发送任务
      worker.postMessage(task);
      });
    }

    /**
   * 获取一个可用的 worker
   * @returns {Promise} 返回一个可用的 worker 实例
   */
    getAvailableWorker() {
      return new Promise((resolve) => {
      // 查找一个未被使用的 worker
      const worker = this.workers.find(w => !this.activeWorkers.has(w));
      if (worker) {
          // 如果找到可用 worker,将其标记为正在使用
          this.activeWorkers.add(worker);
          resolve(worker);
      } else {
          // 如果没有可用 worker,将请求加入等待队列
          this.queue.push(resolve);
      }
      });
    }

    /**
   * 释放 worker,使其可以执行新的任务
   * @param {Worker} worker - 要释放的 worker 实例
   */
    releaseWorker(worker) {
      // 从活动集合中移除 worker
      this.activeWorkers.delete(worker);
      // 如果等待队列中有任务,立即分配给这个释放的 worker
      if (this.queue.length > 0) {
      const resolve = this.queue.shift();
      this.activeWorkers.add(worker);
      resolve(worker);
      }
    }
}

// worker.js - Web Worker 线程池中的工作线程脚本
// 负责处理主线程发送的各种计算密集型任务

/**
* 监听来自主线程的消息
* 根据消息类型执行不同的任务处理
* @param {MessageEvent} e - 接收到的消息事件对象
*/
self.onmessage = async function(e) {
    try {
      // 解构消息数据,获取任务类型和数据具体的业务逻辑
      const { type, data } = e.data;
      
      // 根据任务类型分发到不同的处理函数
      switch (type) {
      case 'PROCESS_ARRAY':
          // 处理大型数组数据
          const result = await processLargeArray(data);
          // 将处理结果返回给主线程
          self.postMessage({ success: true, data: result });
          break;
         
      case 'IMAGE_PROCESSING':
          // 处理图片数据
          const processedImage = await processImage(data);
          // 将处理后的图片返回给主线程
          self.postMessage({ success: true, data: processedImage });
          break;
         
      default:
          // 未知的任务类型,抛出错误
          throw new Error('Unknown task type');
      }
    } catch (error) {
      // 捕获并处理任务执行过程中的错误
      // 将错误信息返回给主线程
      self.postMessage({ success: false, error: error.message });
    }
};
相关的注释 在代码中已经写的很清楚了,这里不在赘述。
线程池可以优化哪些地方?
1、资源重用:创建线程必要消耗资源,线程池可以通过已有的线程来减少线程的创建和销毁线程这部分性能的开销,通过poolSize来控制worker的数量
constructor(workerScript, poolSize = navigator.hardwareConcurrency) {
    // 预先创建固定数量的worker线程
    for (let i = 0; i < poolSize; i++) {
      const worker = new Worker(workerScript);
      this.workers.push(worker);
    }
}
2、并发控制:限定线程数量,克制创建过多线程导致系统资源耗尽
poolSize = navigator.hardwareConcurrency// 默认使用CPU核心数作为池大小
3、任务队列:当线程都在忙的时候 ,新的任务会进入等待队列
getAvailableWorker() {
    // 如果没有可用worker,将请求加入等待队列
    if (!worker) {
      this.queue.push(resolve);
    }
}
4、线程状态跟踪管理:跟踪每个worker的状态,空闲了就分配任务
this.activeWorkers = new Set();// 跟踪正在使用的worker
5、任务分配机制:当线程完成之后,会自动分配任务
releaseWorker(worker) {
    this.activeWorkers.delete(worker);
    // 如果队列中有等待的任务,立即分配给释放的worker
    if (this.queue.length > 0) {
      const resolve = this.queue.shift();
      this.activeWorkers.add(worker);
      resolve(worker);
    }
}
综上所述线程池的主要上风:


[*]进步相应速度:重用已存在的线程,克制线程创建的延迟
[*]资源管理:通过限定线程数量,防止资源耗尽
[*]进步系统稳定性:克制频仍创建和销毁线程带来的系统开销
[*]提供可管理性:提供了一种统一的任务分配和执行机制
总结:web worker本质上就是对与web worker的再封装,使其可以或许在并发的情况下减少资源的消耗,实现性能的优化。
web worker入门先容就写到这里,后面我会团结web worker使用前端AI模型TensorFlow.js 实现纯前端的图片分析,文本分析,遇到的具体标题在做总结!欢迎各位大佬评论区交流
本文参考:
一文彻底学会使用web worker
Web Worker 使用指南
两万字Web Workers终极指南
Web Worker API MDN

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Web Worker 入门:让前端应用多线程化