0


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

// 发送消息到 Worker
worker.postMessage({ type: ‘START’, data: largeArray });

// 接收 Worker 消息
worker.onmessage = function(e) {
console.log(‘Worker 返回结果:’, e.data);
};
// worker.js  worker线程代码
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

标签: 前端

本文转载自: https://blog.csdn.net/weixin_44865458/article/details/144116589
版权归原作者 十九万里 所有, 如有侵权,请联系我们删除。

“Web Worker 入门:让前端应用多线程化”的评论:

还没有评论