webWorker解决单线程中的一些小问题和性能优化

2023-12-02 09:12

本文主要是介绍webWorker解决单线程中的一些小问题和性能优化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

js是单线程这是大家都知道,为了防止多个线程同时操作DOM,这个导致一个复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准。

webWorker

web worker是 HTML5 标准的一部分,这一规范定义了一套 API,允许我们在 js 主线程之外开辟新的 Worker 线程,并将一段 js 脚本运行其中,它赋予了开发者利用 js 操作多线程的能力。

因为是独立的线程,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。这样,js 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了。

常见问题

场景一:人脸识别和活体检测

在这两个场景中会通过ws实时上传图片(加密)

这里面主要是两个问题:
(1)图片加密频繁操作,导致占用主线程,页面出现卡顿卡死情况

(2)定时器延时,上面操作导致定时器并不是开始规定的5秒可能是7秒8秒

场景二:大规模数据上传

比如有个项目前端拿到很大量据需要,需要后处理后再上传服务器。

处理数据可能会占用主线程导致页面显示不流程,卡顿等情况

场景三:请求过于频繁,读写操作过多

同上

场景四:视频音频转码、转格式、加密等操作。

和场景一一样,大量耗费计算资源。阻塞主线程,导致页面流畅度下降,响应降低。

解决以上问题可以考虑使用webwork

怎么使用?

要使用先了解它
Web Worker的限制

1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象。

2、Worker中只能获取到部分浏览器提供的 API,如定时器、navigator、location、XMLHttpRequest等。

3、由于可以获取XMLHttpRequest 对象,可以在 Worker 线程中执行ajax请求。

4、每个线程运行在完全独立的环境中,需要通过postMessage、 message事件机制来实现的线程之间的通信。

worker.postMessage:

向 worker 的内部作用域发送一个消息,消息可由任何 JavaScript 对象组成

worker.terminate:

立即终止 worker。该方法并不会等待 worker 去完成它剩余的操作;worker 将会被立刻停止

worker.onmessage:

当 worker 的父级接收到来自其 worker 的消息时,会在 Worker 对象上触发message 事件

worker.onerror:

当 worker 出现运行中错误时,它的 onerror事件处理函数会被调用。它会收到一个扩展了

ErrorEvent 接口的名为 error 的事件

我直接上代码,我常用(不,是只会)vue,看在项目是怎么使用的。

我写了几个worker.js


1:msg.worker.js 为了消息推送

onmessage = function(e) {console.log(e)postMessage(e.data.num);// close();
}
import MsgWorker from "./demo/msg.worker"; // mounted  初始化
this.msgWorker = new MsgWorker ();this.worker.onmessage = (event) => {console.log(event.data);this.result = event.data;console.log("主线程收到回复,即将关闭worker连接", this.index);// this.worker.terminate();
};methods: {useWorker() {this.msgWorker.postMessage({ text: "当前时间:", num: Date.now() });},},

想想:他能干嘛?

利用策略模式,全局监听操作,和vue Bus 功能一样,使用在页面交互功能一样。

有人说和写个全局方法不一样吗?
不一样,它不占用js的主线程


2:time.worker.js添加定时器

onmessage = function (e) {setTimeout(() => {postMessage(Date.now());}, e.data.time);// close();
};
import TimeWorker from "./demo/time.worker";//初始化mounted
mounted() {this.timeWorker = new TimeWorker();this.timeWorker.onmessage = (event) => {this.result = event.data;console.log("定时完成");};},
//方法调用methods: {useWorker() {this.timeWorker.postMessage({ time: 10000 });},},

 定时器能干嘛?如果进来先执行 上面定时器useWorker()  再执行下面计算demo()
 它能定时输出时间,不被js主线程阻塞影响。

 demo() {console.log("Start", Date.now());let i = 0;for (let index = 0; index < 100000000000; index++) {i += index;}console.log(i, Date.now())console.log("End", Date.now());
}


3:canvas.worker.js canvas绘制

<template><div class="canvas-demo"><button @click="makeWorker">开始绘图</button><canvas id="myCanvas" width="300" height="150"></canvas></div>
</template><script>
import Worker from "./demo/canvas.worker";export default {methods: {makeWorker() {let worker = new Worker();let htmlCanvas = document.getElementById('myCanvas');// 使用canvas的transferControlToOffscreen函数获取一个OffscreenCanvas对象let offscreen = htmlCanvas.transferControlToOffscreen();// 注意:第二个参数不能省略worker.postMessage({ canvas: offscreen }, [offscreen]);}}
};
</script><style lang="less">
.canvas-demo {padding: 20px;
}
</style>

canvas.worker.js

onmessage = function (e) {// 使用OffscreenCanvas(离屏Canvas)let canvas = e.data.canvas;// 获取绘图上下文let ctx = canvas.getContext('2d');// 绘制一个圆弧ctx.beginPath(); // 开启路径ctx.arc(150, 75, 50, 0, Math.PI * 2);ctx.fillStyle = '#333333'; //设置填充颜色ctx.fill(); //开始填充ctx.stroke();
};

4:xhrWorker.js接口调用 

<template><div><button @click="useWorker">开始线程</button></div>
</template><script>
import { fetchApi} from "./demo/xhrWorker.js";
export default {data() {return {worker: null,};},mounted() {const blob = fetchApi();this.worker = new Worker(blob); // 使用上面import进来的js,名字为 demo.worker.worker.js,不可配置,路径相对比较灵活,需要worker-loaderthis.worker.onmessage = (event) => {console.log(event.data);console.log("主线程收到回复,即将关闭worker连接", event.data);// this.worker.terminate();};this.worker.onerror = (event) => {console.log(event.data);};},methods: {useWorker() {this.worker.postMessage({url: `http://xx.xx.xx.xx:8888/login`,data: {password: "admin",username: "admin123456",},responseType: "json",method: "POST",id: Date.now(),});},},// 页面关闭,如果还没有计算完成,要销毁对应线程beforeDestroy() {},
};
</script>

 xhrWorker.js  我尝试将他写成 xhr.worker.js 结果获取不到fetchApi方法,可以注意下

export function fetchApi() {const workerCode = `
self.addEventListener('message', async function (e) {const { url, data, responseType, method, id } = e.dataconst xhr = new XMLHttpRequest()xhr.open(method, url, true)xhr.responseType = responseTypexhr.setRequestHeader('Content-Type', 'application/json')xhr.onload = function () {if (xhr.status === 200) {self.postMessage({xhrRes: xhr.response, id})} else {self.postMessage({ error: 'error' })}}xhr.onerror = function () {self.postMessage({ error: 'error' })}xhr.send(JSON.stringify(data))
})
`const blob = new Blob([workerCode], { type: 'application/javascript' })const blobUrl = URL.createObjectURL(blob)return blobUrl
}

 5:dataWorker.js数据计算 

<template><div><div class="data-lsit"><div class="=data-item" v><span>数据</span></div></div><button @click="makeWorker">开始线程</button><!--在计算时 往input输入值时 没有发生卡顿--><p><input type="text" /></p></div>
</template><script>
import Worker from "./demo/math.worker";export default {data() {// 模拟数据let arr = new Array(20).fill(1).map(() => Math.random() * 10000);let weightedList = new Array(100000).fill(1).map(() => Math.random() * 10000);let calcList = [{ type: "sum", name: "总和" },{ type: "average", name: "算术平均" },{ type: "weightedAverage", name: "加权平均" },{ type: "max", name: "最大" },{ type: "middleNum", name: "中位数" },{ type: "min", name: "最小" },{ type: "variance", name: "样本方差" },{ type: "popVariance", name: "总体方差" },{ type: "stdDeviation", name: "样本标准差" },{ type: "popStandardDeviation", name: "总体标准差" },];return {workerList: [], // 用来存储所有的线程calcList, // 计算类型arr, // 数据weightedList, // 加权因子};},methods: {makeWorker() {this.calcList.forEach((item) => {let workerName = `worker${this.workerList.length}`;let worker = new Worker();let start = performance.now();worker.postMessage({arr: this.arr,type: item.type,weightedList: this.weightedList,});worker.addEventListener("message", (e) => {worker.terminate();let tastName = "";this.calcList.forEach((item) => {if (item.type === e.data.type) {item.value = e.data.value;tastName = item.name;}});let end = performance.now();let duration = end - start;console.log(`当前任务: ${tastName}, 计算用时: ${duration} 毫秒`);});this.workerList.push({ [workerName]: worker });});},clearWorker() {if (this.workerList.length > 0) {this.workerList.forEach((item, key) => {item[`worker${key}`].terminate && item[`worker${key}`].terminate(); // 终止所有线程});}},},// 页面关闭,如果还没有计算完成,要销毁对应线程beforeDestroy() {this.clearWorker();},
};
</script>
import { create, all } from 'mathjs'
const config = {number: 'BigNumber',precision: 20 // 精度
}
const math = create(all, config);//加
const numberAdd = (arg1,arg2) => {return math.number(math.add(math.bignumber(arg1), math.bignumber(arg2)));
}
//减
const numberSub = (arg1,arg2) => {return math.number(math.subtract(math.bignumber(arg1), math.bignumber(arg2)));
}
//乘
const numberMultiply = (arg1, arg2) => {return math.number(math.multiply(math.bignumber(arg1), math.bignumber(arg2)));
}
//除
const numberDivide = (arg1, arg2) => {return math.number(math.divide(math.bignumber(arg1), math.bignumber(arg2)));
}// 数组总体标准差公式
const popVariance = (arr) => {return Math.sqrt(popStandardDeviation(arr))
}// 数组总体方差公式
const popStandardDeviation = (arr) => {let s,ave,sum = 0,sums= 0,len = arr.length;for (let i = 0; i < len; i++) {sum = numberAdd(Number(arr[i]), sum);}ave = numberDivide(sum, len);for(let i = 0; i < len; i++) {sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))}s = numberDivide(sums,len)return s;
}// 数组加权公式
const weightedAverage = (arr1, arr2) => { // arr1: 计算列,arr2: 选择的权重列let s,sum = 0, // 分子的值sums= 0, // 分母的值len = arr1.length;for (let i = 0; i < len; i++) {sum = numberAdd(numberMultiply(Number(arr1[i]), Number(arr2[i])), sum);sums = numberAdd(Number(arr2[i]), sums);}s = numberDivide(sum,sums)return s;
}// 数组样本方差公式
const variance = (arr) => {let s,ave,sum = 0,sums= 0,len = arr.length;for (let i = 0; i < len; i++) {sum = numberAdd(Number(arr[i]), sum);}ave = numberDivide(sum, len);for(let i = 0; i < len; i++) {sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))}s = numberDivide(sums,(len-1))return s;
}// 数组中位数
const middleNum = (arr) => {arr.sort((a,b) => a - b)if(arr.length%2 === 0){ //判断数字个数是奇数还是偶数return numberDivide(numberAdd(arr[arr.length/2-1], arr[arr.length/2]),2);//偶数个取中间两个数的平均数}else{return arr[(arr.length+1)/2-1];//奇数个取最中间那个数}
}// 数组求和
const sum = (arr) => {let sum = 0, len = arr.length;for (let i = 0; i < len; i++) {sum = numberAdd(Number(arr[i]), sum);}return sum;
}// 数组平均值
const average = (arr) => {return numberDivide(sum(arr), arr.length)
}// 数组最大值
const max = (arr) => {let max = arr[0]for (let i = 0; i < arr.length; i++) {if(max < arr[i]) {max = arr[i]}}return max
}// 数组最小值
const min = (arr) => {let min = arr[0]for (let i = 0; i < arr.length; i++) {if(min > arr[i]) {min = arr[i]}}return min
}// 数组有效数据长度
const count = (arr) => {let remove = ['', ' ', null , undefined, '-']; // 排除无效的数据return arr.filter(item => !remove.includes(item)).length
}// 数组样本标准差公式
const stdDeviation = (arr) => {return Math.sqrt(variance(arr))
}// 数字三位加逗号,保留两位小数
const formatNumber = (num, pointNum = 2) => {if ((!num && num !== 0) || num == '-') return '--'let arr = (typeof num == 'string' ? parseFloat(num) : num).toFixed(pointNum).split('.')let intNum = arr[0].replace(/\d{1,3}(?=(\d{3})+(.\d*)?$)/g,'$&,')return arr[1] === undefined ? intNum : `${intNum}.${arr[1]}`
}onmessage = function (e) {let {arr, type, weightedList} = e.datalet value = '';switch (type) {case 'sum':value = formatNumber(sum(arr));breakcase 'average':value = formatNumber(average(arr));breakcase 'weightedAverage':value = formatNumber(weightedAverage(arr, weightedList));breakcase 'max':value = formatNumber(max(arr));breakcase 'middleNum':value = formatNumber(middleNum(arr));breakcase 'min':value = formatNumber(min(arr));breakcase 'variance':value = formatNumber(variance(arr));breakcase 'popVariance':value = formatNumber(popVariance(arr));breakcase 'stdDeviation':value = formatNumber(stdDeviation(arr));breakcase 'popStandardDeviation':value = formatNumber(popStandardDeviation(arr));break}// 发送数据事件postMessage({type, value});
}

总结

以上列子分了五个场景,直接引入项目就可以测试,最后一个例子请参考:一文彻底了解Web Worker,十万条数据都是弟弟附带源码。

写这篇文章为了巩固下webWorker的使用,希望能对你们有所帮助,如果有帮助请点个赞。谢了

这篇关于webWorker解决单线程中的一些小问题和性能优化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/444737

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

504 Gateway Timeout网关超时的根源及完美解决方法

《504GatewayTimeout网关超时的根源及完美解决方法》在日常开发和运维过程中,504GatewayTimeout错误是常见的网络问题之一,尤其是在使用反向代理(如Nginx)或... 目录引言为什么会出现 504 错误?1. 探索 504 Gateway Timeout 错误的根源 1.1 后端

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

Java实现复杂查询优化的7个技巧小结

《Java实现复杂查询优化的7个技巧小结》在Java项目中,复杂查询是开发者面临的“硬骨头”,本文将通过7个实战技巧,结合代码示例和性能对比,手把手教你如何让复杂查询变得优雅,大家可以根据需求进行选择... 目录一、复杂查询的痛点:为何你的代码“又臭又长”1.1冗余变量与中间状态1.2重复查询与性能陷阱1.

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变