Web Worker多线程的开挖到开埋
回归点本行,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的这种特性就不好直接实现了,迁就数字也会一些视觉效果的影响。
下面开始web worker的常规套路
什么是 Web Worker
浏览器提供的一种技术,可以让你在后台开启一个独立的线程运行 JavaScript 代码。就是单独开一个线程去单独处理事情,然后再把结果返回给主线程。主线程原来在干嘛就该干嘛还干嘛。
基本用法
基本用法其实很简单,跟大象放冰箱分三步走一样
-
新建一个worker脚本
-
主线程创建实例
-
增加交互(发送消息、接收、释放资源)
搞一个最简单的实例,发送进去一个数组,然后给我返回一个数值都是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脚本路径的正确解析和构建。
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])
}
砰~~~~
如果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 线程 |
好象到这儿就可以结束了!!!
把通讯内容的类型再拉拉拉扯扯~~~
通信内容类型
主线程与 Web Worker 线程之间通信是用的“值传递”(拷贝)机制,数据会被序列化后传输,Worker 线程对数据的修改不会影响主线程。web worker的信息处理异常与否都不会影响主线程的数据显示,如果遇到大数据的时候性能开销会比较大,这时候可以考虑使用转移数据所有权,避免复制。
方法:
worker.postMessage(arrayBuffer, [arrayBuffer]);
像 ArrayBuffer、MessagePort 或 ImageBitmap 类的实例才是可转移对象,才能够被转移。不能将 null 作为 transfer 的值。
- 字符串
- 原始值
- 通过结构化克隆传递,数据是拷贝
- 对象
- 不支持函数、DOM 节点、循环引用等不可序列化内容
- 通过结构化克隆传递,数据被深拷贝
- ArrayBuffer
- 支持结构化克隆,也支持作为可转移对象(Transferable Objects)传递,传递所有权避免数据复制,提高性能
- TypedArray
- 同样支持结构化克隆和可转移对象传递
- Blob
- 支持结构化克隆传递
- File
使用场景
大数据处理
好象开始提到的物联网数据,小量的数据还好,有时候需要查询大量的历史数据进行统计,或者大段时间的巡检轨迹,接口返回的径路报文信息,对于这些数据需要进行格式化处理,以满足界面所需要的格式要求,这个时候直接搞这个。
实时数据
还是物联网行业,WebSocket的实时数据流处理,Worker 负责解析和计算,主线程负责页面。
复杂计算任务
图像处理、图像处理、音频处理、巴拉巴拉等,我日常用不到的不列了
还有前端动画嘛,文件等,自行发挥吧,基于基础方法之上,开发更多的适合于自己的场景。
craft又又又更新了,还更新得越来越好,你更新得越好,就显示微信公众号的排版越烂。