Web Worker多线程的开挖到开埋

Web Worker多线程的开挖到开埋

Image.png

回归点本行,web worker之前只是照着文档使用一回,想着零档起手,那就从头开始整理一下,费曼嘛,分享到网上也单纯的只是想,也许互联网上的某个角落只要有一个人觉得有用,那就算有用了。

公司是处理物联网相关内容,会有大量的实时数据需要二次处理,或者历史数据重新整理分析等等,特别是现在的招标企业领导又特别喜欢看大屏、报表之类的,物联网的大屏是又要数据又要效果,然后还不能让页面响应等太久,鉴于JS的特性,所以大屏交互上的,能让CSS干的尽量让CSS干,美其名曰基于动画展示效果和性能考虑,实质上是单纯喜欢用CSS去捣鼓,然后某日想起web worker的特性:

  • 多线程环境:Web Worker 允许创建一个独立于主线程的线程,用于执行特定任务。

  • 同源限制:Worker 脚本必须与主线程脚本同源。

  • DOM 限制:Worker 线程无法访问主线程的 DOM 对象,但可以访问 navigator 和 location 对象。

  • 通信机制:Worker 与主线程通过消息进行通信。

  • 脚本限制:Worker 不能执行 alert() 和 confirm() 方法,但可以使用 XMLHttpRequest 对象。

  • 文件限制:Worker 不能读取本地文件,脚本必须来自网络

这不就是专门搞非界面交互无关的数据内容的好手吗?

JavaScript的单线程

众所周知,js是一个单线程的玩意,代码按顺序执行,形成一个调用栈,每次只能执行栈顶的任务,具体的原理不说了,用个代码表示就是

console.log('1');

setTimeout(() => {
console.log('2');
}, 0);

console.log('3');

// 不出意外打印结果应该1 3 2

另外一个示例说明,页面上同时有一个数不停的从1+到1000,然后有一个DIV想要JS控制宽度一样从1+到1000,不用CSS动画的话,也是要循环改变,频率一样,同时变化还好,可以共用,但如果频率不一样呢,因为JS的这种特性就不好直接实现了,迁就数字也会一些视觉效果的影响。

output2.gif

output.gif

下面开始web worker的常规套路

什么是 Web Worker

浏览器提供的一种技术,可以让你在后台开启一个独立的线程运行 JavaScript 代码。就是单独开一个线程去单独处理事情,然后再把结果返回给主线程。主线程原来在干嘛就该干嘛还干嘛。

基本用法

基本用法其实很简单,跟大象放冰箱分三步走一样

  1. 新建一个worker脚本

  2. 主线程创建实例

  3. 增加交互(发送消息、接收、释放资源)

搞一个最简单的实例,发送进去一个数组,然后给我返回一个数值都是double的数组。

// worker.js

self.onmessage = (event) => {
    const sensorData = event.data;

    if (Array.isArray(sensorData) && sensorData.length > 0) {
        // 把数组每个元素乘以2
        const doubledData = sensorData.map(item => item * 2);

        // 发送处理后的数组回主线程
        self.postMessage({ doubledData });
    } else {
        // 如果没有数据,返回空数组或提示
        self.postMessage({ doubledData: [] });
    }
}

页面交互用VUE3处理一个

import { ref, onBeforeUnmount } from 'vue';

// 返回结果
const results = ref(0);

// 创建Worker实例
const worker = new Worker(new URL('./worker.js', import.meta.url));

// 点击事件
const start=() =>{
  worker.postMessage([1,2,3,4,5,6,7])
}

// 接收Worker消息
worker.onmessage = (event) => {
  results.value = event.data.doubledData;
};

// 组件卸载时清理
onBeforeUnmount(() => {
  worker.terminate();
});

new URL('./worker.js', import.meta.url) 为了兼容不同环境,保证worker脚本路径的正确解析和构建。

Image.png

worker脚本内部有哪些方法

除了刚刚示例里面的self.onmessageg 来监听主线程发过来的消息,和self.postMessage 给主线程发送消息,还有关闭当前线程的方法self.close() 等同于主线程调用的worker.terminate() 当然还有监听内部错误的self.onerror

  • self.onmessage / self.addEventListener('message', handler)

    监听主线程发送过来的消息。

  • self.postMessage(data)

    向主线程发送消息,传递数据。

  • self.close()

    关闭当前 Worker 线程,等同于主线程调用 worker.terminate()

  • self.onerror / self.addEventListener('error', handler)

    监听 Worker 内部的错误事件。

把刚刚的上面的示例修改一下试试

// worker.js

self.onmessage = (event) => {
    const sensorData = event.data;

    if (Array.isArray(sensorData) && sensorData.length > 0) {
        // 把数组每个元素乘以2
        const doubledData = sensorData.map(item => {
            if (typeof item !== 'number') {
                throw new Error(`元素类型错误`);
            }
            return item * 2;
        });

        // 发送处理后的数组回主线程
        self.postMessage({ doubledData });
    } else {
        // 如果没有数据,返回空数组或提示
        self.postMessage({ doubledData: [] });
    }
}

self.onerror = (event) => {
    console.error(event);
    self.close();
};

当然主线程也可以增加一个监听错误的方法

// 增加上异常的方法
worker.onerror = (event) => {
  console.log(event)
};

// 修改方法发送一个异常值过去
const start=() =>{
  worker.postMessage([1, 2, undefined, 4])
}

砰~~~~

Image.png

如果worker.js里面的try...catch捕获异常了的话,则不会自动触发onerror,所以要么不用try...catch 要么在catch里面把异常抛出来!

主线程里面的方法

刚刚上面都用到过了,最基本的消息通讯的postMessage和onmessage,和刚刚写的onerror和terminate

好看一点的排列就是

worker.postMessage()向 Worker 发送消息
worker.onmessage / addEventListener('message')接收 Worker 消息
worker.onerror / addEventListener('error')监听 Worker 错误
worker.terminate()终止 Worker 线程

Image.png

好象到这儿就可以结束了!!!

把通讯内容的类型再拉拉拉扯扯~~~

通信内容类型

主线程与 Web Worker 线程之间通信是用的“值传递”(拷贝)机制,数据会被序列化后传输,Worker 线程对数据的修改不会影响主线程。web worker的信息处理异常与否都不会影响主线程的数据显示,如果遇到大数据的时候性能开销会比较大,这时候可以考虑使用转移数据所有权,避免复制。

方法:

worker.postMessage(arrayBuffer, [arrayBuffer]);

像 ArrayBuffer、MessagePort 或 ImageBitmap 类的实例才是可转移对象,才能够被转移。不能将 null 作为 transfer 的值。

  • 字符串
  • 原始值
    • 通过结构化克隆传递,数据是拷贝
  • 对象
    • 不支持函数、DOM 节点、循环引用等不可序列化内容
    • 通过结构化克隆传递,数据被深拷贝
  • ArrayBuffer
    • 支持结构化克隆,也支持作为可转移对象(Transferable Objects)传递,传递所有权避免数据复制,提高性能
  • TypedArray
    • 同样支持结构化克隆和可转移对象传递
  • Blob
    • 支持结构化克隆传递
  • File

使用场景

大数据处理

好象开始提到的物联网数据,小量的数据还好,有时候需要查询大量的历史数据进行统计,或者大段时间的巡检轨迹,接口返回的径路报文信息,对于这些数据需要进行格式化处理,以满足界面所需要的格式要求,这个时候直接搞这个。

实时数据

还是物联网行业,WebSocket的实时数据流处理,Worker 负责解析和计算,主线程负责页面。

复杂计算任务

图像处理、图像处理、音频处理、巴拉巴拉等,我日常用不到的不列了

还有前端动画嘛,文件等,自行发挥吧,基于基础方法之上,开发更多的适合于自己的场景。


craft又又又更新了,还更新得越来越好,你更新得越好,就显示微信公众号的排版越烂。