超越单线程:Web Worker 在前端性能中的角色

news2024/11/18 16:42:08

在当今快速发展的数字时代,用户对网页性能的期待已经达到了前所未有的高度,想象一下,当你打开一个网站,瞬间加载、流畅操作,没有任何卡顿和延迟,这种体验无疑会让你倍感惊喜。然而在前端开发中,如何实现这样的流畅体验却是一个巨大的挑战。这时,Web Worker 作为一项强大的技术,悄然崭露头角。

目录

🧐初识webworker

🤓webworker使用

🥳SharedWorker使用

😎webworker案例


🧐初识webworker

单线程和多线程概念

1)定义

单线程是指在一个进程中只有一个执行线程。这个线程负责处理所有的任务和操作,包括用户输入、界面更新和数据处理。

多线程是指在一个进程中可以同时存在多个执行线程。这些线程可以并行处理多个任务,提高程序的效率和响应能力。

2)应用场景

单线程适合简单、快速开发的应用,但在处理复杂或耗时任务时可能导致性能瓶颈。

多线程则在性能和响应性方面具有优势,但同时也带来了更高的开发复杂度和潜在的安全问题。

我们知道JavaScript是单线程语言,也就是说所有任务只能在一个线程上完成,一次只能做一件事情,然而webworker的出现就是为JS创造多线程环境,允许主线程创建worker线程,将一些任务分配给后者运行,在主线程运行的同时worker线程在后台运行,两者互不干扰,如下图所示:

使用webworker可以单独的开启一个线程,线程直接通过message消息进行通信,webworker中的代码不会影响ui的响应,案例代码如下所示:

// 主线程
var worker = new Worker('work.js')
worker.onmessage = e => {
    console.log(e.data)
}

// worker线程
// 做一些耗时的操作
self.postMessage('worker:' + result)

多线程和异步操作有什么关系

多线程是同时运行多个线程以并行处理任务,创建独立的线程来执行js代码,可以进行计算密集型任务,而不会阻塞主线程。

异步操作是在不阻塞主线程的情况下执行某些任务,常见的异步操作包括网络请求、定时器等。

异步操作:众所周知,js一直被说不擅长计算确实,计算是同步的,大规模的计算会让js主线程阻塞,主线程阻塞的结果就是界面完全卡死一样,所以异步只是把任务发布出去等着,后面还是会拉到主线程执行的,异步不可能在异步队列自己执行,所以一个耗时很高的操作,无论你做不做异步,他始终会卡死你的页面,如下所示:

从上图我们可以看出来,假设这个耗时任务必须消耗2秒去计算,我们主线程永远不可能躲开这两秒的计算时间,只能同过切片等操作,把这两秒切分成好几个几十毫秒,一点点计算来解决卡顿的问题

线程操作:webworker是真正的多线程,开一条支线,让他计算然后把结果返回,当计算完成把结果发给主线程,如下图所示:

兼容性问题:我们可以从 caniuse网站:地址  可以看到webworker的兼容性还是不错的,现代浏览器基本上都已经支持了,除了低版本的安卓等其他老式低版本浏览器:

当然具体的webworker使用也可以参考mdn文档:地址  里面也是非常详细的介绍了其使用:

worker注意点(局限性)

1)无法访问 DOM:Web Worker 运行在独立的线程中,因此它们无法直接访问 DOM,所有与用户界面相关的操作必须在主线程中进行。

2)消息传递机制:Worker 之间以及 Worker 与主线程之间的通信是通过消息传递进行的。这意味着数据需要被序列化(例如使用 postMessage),而某些复杂对象(如函数和 DOM 元素)无法直接传递。

3)资源限制:Workers 在浏览器中有一些资源限制,比如内存使用和最大线程数。在资源紧张的情况下,浏览器可能会限制 Worker 的创建或运行。

4)调试困难:在开发过程中,调试 Web Worker 可能比较复杂,因为它们的执行环境与主线程不同,错误和日志信息可能不容易追踪。

5)没有全局作用域:Web Worker 没有全局作用域,它们只能访问 Web Workers API 和一些基本的 JavaScript 功能。这限制了可以使用的库和框架。

6)启动时间:创建 Worker 有一定的启动时间,尤其是在需要频繁创建和销毁 Worker 的场景下,这可能导致性能下降。

7)浏览器支持:尽管大多数现代浏览器都支持 Web Worker,但在某些老旧的浏览器或特定的环境中(如某些版本的移动浏览器),可能不完全支持。

虽然 Web Worker 提供了一种有效的方法来处理并行计算和长时间运行的任务,但开发者需要了解这些局限性,以便在设计应用时做出合理的权衡和选择。

🤓webworker使用

消息发送:要定义webworker可以直接新建一个普通的js文件,在其中监听onmessage事件,通过事件参数的data属性访问传递进来的消息,然后使用postMessage回传消息给主线程,注意:worker里面有一个全部变量self,类似浏览器当中的window全局变量对象,类似node当中的global局变量对象一样的作用,示例如下:

控制台打印的结果如下所示,我们可以通过e.data来获取到我们传递到主进程的数据:

通过上面代码我们完全看不出使用worker线程有什么优势,别着急,接下来我们可以看看如果想在项目中实现一段斐波那契数列,需要耗费多少时间,代码如下:

<script>
    var worker1 = new Worker("worker1.js");
    worker1.onmessage = e => {
        console.log(e);
    }

    function fb(n) {
        if (n == 1 || n == 2) {
            return 1;
        }
        return fb(n - 1) + fb(n - 2);
    }
    console.time('fb执行时间');
    var result = fb(43);
    console.timeEnd('fb执行时间');
</script>

从控制台可以看出,我们斐波那契数列最终的执行结果需要耗费近四千毫秒左右,而js项目由于是单线程内容,必须得等到我们斐波那契数列执行完毕才会渲染页面,也就是说我们足足等了四秒钟页面才会被加载出来:

如果有一个需求,让你在同一个组件当中执行类似上面非常耗时的斐波那契四遍,你会怎么做?不了解多线程的可能就会在同一个组件执行呗,无非多花一点时间,这里就导致了执行四遍斐波那契函数的时间出现了累加,整个页面需要足足等待4*4=16秒,这不是我们想要看到的结果,所以这里我们将耗时的代码(斐波那契递归代码)放置在worker线程当中,看看有没有什么出奇的效果,代码如下:

如下代码我们可以看到,多个斐波那契数列被同时执行了,这也就意味着一个12秒的活,我分派给三个人去做,最终所花费的时间仅仅是四秒钟而已,公司招聘多名员工进行协作开发项目也是同一个道理。

文件引入:在使用框架进行开发项目中,worker构造函数创建的webworker实例传递的url路径必须是同源的,之后才能保持返回的实例,也就是说后期我们项目如果想上线的话,我们设置的worker文件就不能是本地文件,必须将其放置在线上地址也就是我们的public文件夹下,其他文件夹都只算是本地文件,如果真正发布到服务器上去了,就可以让后端人员把我们的worker文件丢到某个静态资源下就好了,如下所示:

模块引入:Worker函数有第二个配置项的函数,其对应的可以控制模块引入的模式,如下:

🥳SharedWorker使用

SharedWorker接口代表一种特定类型的worker,可以从几个浏览上下文中访问,例如几个窗口、iframe或其他worker,它们实现一个不同于普通worker的接口,具有不同的全局作用域。

区别:SharedWorker与worker的区别在于在调用的使用SharedWorker会使用一个连字符port,用于表示在当前这个上下文中的这个worker。

因为SharedWorker会根据传递的文件路径作为唯一性判定,像下面代码我创建了十次SharedWorker,但是他们都是指向同一个文件,所以他们都会复用用一个SharedWorker,所以下面代码虽然循环了十次但是他们使用的worker是同一个的,代码如下所示:

// 主进程
<script>
    console.time("test")
    let count = 0
    for (var i = 0; i < 10; i++) {
        let worker = new SharedWorker("./share.js")
        worker.port.postMessage(40)
        worker.port.onmessage = function (e) {
            console.log(e.data)
            count++
            if (count === 10) console.timeEnd("test")
        }
    }
</script>

// sharedworker进程
function fib(n) {
    return n < 2 ? 1 : fib(n - 1) + fib(n - 2);
}

self.onconnect = function(e) {
  var port = e.ports[0];
  port.onmessage = function(e) {
    let num = e.data;
    let res = fib(num)
    port.postMessage(res);
  };
};

从日志当中我们可以看到其打印了10次,结果是15秒左右,这个和直接把斐波那契函数全部写在一起串行调用效果是一样的,这是因为所有的任务都是一个shareWorder来运行的:

sharedworder作用: 在不同的页面之间,只有url相同,只有挂载一个sharedworker,这个会被所有页面所共享,这个是和worder所不同的地方!

😎webworker案例

图片像素操作:在处理图片像素点的时候,如下代码所示,可能由于计算量过于庞大导致页面出现卡死的状态,用户由于一段程序的导致页面的卡死而不能操作其他页面了,如下代码就是这样的效果:

<template>
    <input type="text">
    <button @click="imgHandler">过滤</button>
    <canvas id="imgCanvas" width="1200" height="600"></canvas>
</template>

<script setup lang="ts">
import image from "./assets/1.jpeg"

let canvas: any
let ctx: any
// 将图片画在画布上
let img = new Image()
img.src = image
img.onload = function () {
    canvas = document.getElementById("imgCanvas") as HTMLCanvasElement
    ctx = canvas.getContext("2d") as CanvasRenderingContext2D
    ctx.drawImage(img, 0, 0, 1200, 600)
}
const imgHandler = () => {
    // 读取所有像素点
    const imageData = ctx.getImageData(0, 0, 1200, 600)
    const data = imageData.data
    // 循环每一个像素点,依次给其做计算,如果一张图有50万像素点的话,每个像素点rgba值为4个,也就是data有200多万像素点
    // 循环200万次
    for (let i = 0; i < data.length; i += 4) {
        // 每次循环内部在循环100次,合集20亿次
        for (let j = 0; j < 255; i++) {
            if (imageData.data[i] !== 255) {
                imageData.data[i] = Math.min(data[i] + j, 0)
            }
        }
    }
    // 每个像素点添加滤镜
    ctx.putImageData(imageData, 0, 0);
}
</script>

接下来我们把计算量庞大的代码进行抽离出去放置到worker线程当中,从而不影响主进程的使用,所以即使某段代码的耗时过大也不会影响到其他页面的使用,如下所示:

<template>
    <input type="text">
    <button @click="imgHandler">过滤</button>
    <canvas id="imgCanvas" width="1200" height="600"></canvas>
</template>

<script setup lang="ts">
import image from './assets/1.jpeg'

let canvas: HTMLCanvasElement
let ctx: CanvasRenderingContext2D

// 将图片画在画布上
let img = new Image();
img.src = image;
img.onload = function () {
    canvas = document.getElementById("imgCanvas") as HTMLCanvasElement;
    ctx = canvas.getContext("2d", { willReadFrequently: true }) as CanvasRenderingContext2D; // 设置 willReadFrequently
    ctx.drawImage(img, 0, 0, 1200, 600);
}

let worker = new Worker("http://127.0.0.1:5173/picwork.ts");
worker.addEventListener("message", (e: MessageEvent) => {
    const imageData = e.data;
    // 每个像素点添加滤镜
    ctx.putImageData(imageData, 0, 0);
});

const imgHandler = () => {
    // 读取所有像素点
    const imageData = ctx.getImageData(0, 0, 1200, 600);
    worker.postMessage(imageData);
}
</script>

在worder线程当中我们把需要大量计算的代码放置在里面:

self.addEventListener("message", (e) => {
    if (e.data.data.length) {
        let imageData = e.data
        for (let i = 0; i < imageData.data.length; i += 4) {
            for (let j = 0; j < 255; j++) {
                if (imageData.data[i] !== 255) {
                    imageData.data[i] = Math.min(imageData[i] + j, 0)
                }
            }
        }
        self.postMessage(imageData);
    }
});

最终呈现的效果如下所示,可以看到图片像素的改变并没有阻塞我们主进程页面的使用:

excel表格操作:如下代码我们使用xlsx第三方库去生成一个excel表格,并往表格中添加十万条数据,这个数据量是非常庞大的,当我们点击导出的时候,可以会导致页面卡死而无法在输入框当中进行操作,代码如下所示:

<template>
    <input type="text">
    <button @click="exportExcel">导出</button>
</template>

<script setup lang="ts">
import { writeFile, utils } from 'xlsx';
let arr = []
for (let i = 0; i < 100000; i++) {
    arr.push({
        id: i,
        name: '测试' + i,
        age: i,
        location: 'xx大道' + i + '号',
        a: i + 2,
        b: i + 3,
        c: i + 4,
    })
}
const exportExcel = () => {
    const sheet = utils.json_to_sheet(arr) // 转换为sheet
    const workbook = utils.book_new() // 创建工作簿
    utils.book_append_sheet(workbook, sheet, 'Sheet1') // 添加到工作簿
    console.log(workbook)
    writeFile(workbook, '测试导出' + new Date().getTime() + '.xlsx') // 写入文件
}
</script>

这个时候我们就需要使用worker去把大量的写入数据放置在线程里面,在导出函数中会在按钮点击时被调用,它向 Worker 发送一个空消息,指示 Worker 开始执行生成 Excel 文件的任务:

<template>
    <input type="text">
    <button @click="exportExcel">导出</button>
</template>

<script setup lang="ts">
import { writeFile } from 'xlsx';

let worker = new Worker("http://127.0.0.1:5173/excelwork.js")
worker.onmessage = (e) => {
    let workbook = e.data
    writeFile(workbook, 'test.xlsx')
}

const exportExcel = () => {
    worker.postMessage('')
}
</script>

线程当中使用 self.postMessage(workbook) 将生成的工作簿发送回主线程,供主线程进一步处理如保存为文件,代码如下所示:

importScripts('./xlsx.js')
let arr = []
for (let i = 0; i < 100000; i++) {
    arr.push({
        id: i,
        name: '测试' + i,
        age: i,
        location: 'xx大道' + i + '号',
        a: i + 2,
        b: i + 3,
        c: i + 4,
    })
}
self.onmessage = function (e) {
    const sheet = XLSX.utils.json_to_sheet(arr) // 转换为sheet
    const workbook = XLSX.utils.book_new() // 创建工作簿
    XLSX.utils.book_append_sheet(workbook, sheet, 'Sheet1') // 添加到工作簿
    self.postMessage(workbook)    
}

主进程负责用户界面交互和文件保存,Worker 线程负责大量数据的处理和 Excel 文件的生成,使用 Web Worker 有效避免了 UI 阻塞,提高了用户体验。

还有一位博主分享了一篇关于webworker应用于three.js方向的,这个也是不错的方法:地址

总结:大部分情况下前端是用不上webworker的,但是如果你确实项目里面涉及到了非常大的计算,这就相当于前端的一个性能瓶颈了,这个时候使用webworker可能是一个不错的选择!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2200381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习——多模态学习

多模态学习&#xff1a;机器学习领域的新视野 引言 多模态学习&#xff08;Multimodal Learning&#xff09;是机器学习中的一个前沿领域&#xff0c;它涉及处理和整合来自多个数据模式&#xff08;如图像、文本、音频等&#xff09;的信息。随着深度学习的蓬勃发展&#xff0…

编译链接的过程发生了什么?

一&#xff1a;程序的翻译环境和执行环境 在 ANSI C 的任何一种实现中&#xff0c;存在两个不同的环境。 第 1 种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令。 第 2 种是执行环境&#xff0c;它用于实际执行代码 也就是说&#xff1a;↓ 1&#xff1…

纠删码参数自适应匹配问题ECP-AMP实验方案(下)

7.参数选择 7.1.综合性能goal 根据权重和性能指标&#xff0c;本方案为每个文件确定最佳的纠删码参数&#xff0c;并将文件分组到不同的数据池中。本文使用了以下公式计算每个文件的评分&#xff0c;表示该文件在使用不同的纠删码参数时的综合性能。 s i j k ∑ j 1 6 c j…

2023 CCPC哈尔滨 报告

比赛链接&#xff1a;Dashboard - 10.6组队训练赛-2023CCPC哈尔滨站 - Codeforceshttps://codeforces.com/group/w6iGs8kreW/contest/552949 做题数&#xff1a;3 题 三题都是队友写的。所以来补一下 B L J。 B题&#xff1a; B. Memory Little G used to be a participant …

【MySQL】基本查询(上):创建、读取

1.Create(创建) 语法&#xff1a; INSERT [INTO] table_name [(column [, column] ...)] VALUES (value_list) [, (value_list)] ...value_list: value, [, value] ... 接下来我们用这个下表作为例子&#xff1a; -- 创建一张学生表 CREATE TABLE students ( id INT UNSIGN…

Http 协议和 RPC 协议有什么区别?

Http 协议和 RPC 协议有什么区别&#xff1f; 三个层面来述说&#xff1a; 从功能特性来说&#xff1a; HTTP是一个属于应用层的超文本传输协议&#xff0c;是万维网数据通信的基础&#xff0c;主要服务在网页端和服务端的数据传输上。 RPC是一个远程过程调用协议&#xff0…

【JS】哈希法解决两数之和

思路 使用哈希法&#xff1a;需要快速查询一个元素是否出现过&#xff0c;或者一个元素是否在集合里时 本题需要一个集合来存放我们遍历过的元素&#xff0c;然后在遍历数组的时候去询问这个集合&#xff0c;符合要求的某元素是否遍历过&#xff0c;也就是 是否出现在这个集合。…

【算法】链表:24.两两交换链表中的节点

目录 1、题目链接 2、题目介绍 3、解法 4、代码 1、题目链接 24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 2、题目介绍 3、解法 引入伪头节点&#xff1a; 为了处理头节点可能被交换的情况&#xff0c;我们引入一个伪头节点&#xff08;dummy no…

jenkins远程调用

curl -G -d tokenfetch_coverage_token&systemmes2&typefull&envsit&resetno http://remote_user:1172e3d5524629fabef5dd55c652646232192.168.36.196:8080/job/fetch_coverage/buildWithParameters 在jenkins的用户界面设置一个token就可以了 remote_user 为…

Android笔记(二十四)基于Compose组件的MVVM模式和MVI模式的实现

仔细研究了一下MVI(Model-View-Intent)模式&#xff0c;发现它和MVVM模式非常的相识。在采用Android JetPack Compose组件下&#xff0c;MVI模式的实现和MVVM模式的实现非常的类似&#xff0c;都需要借助ViewModel实现业务逻辑和视图数据和状态的传递。在这篇文章中&#xff0c…

ESP32-C3实现UART

配置串口参数 在编写代码之前&#xff0c;你需要确定要使用的 UART 端口号和配置参数&#xff08;波特率、数据位、停止位等&#xff09;。 // 定义 UART 端口 #define TX_PIN 1 // TX 管脚 #define RX_PIN 3 // RX 管脚// 定义串口配置参数 #define UART_BAUDRATE 115200 // …

springboot 项目使用 gitlab 的 API

springboot 项目使用 gitlab 的 API 前言获取用户 access tokenSpring boot项目集成GitLab依赖1 pom依赖2 配置文件3 启动类4 核心代码gitlab 的 API 说明前言 需求是通过gitlab的api获取其中的数据。 gitlab官方API文档:https://docs.gitlab.com/ee/api/users.html gitla…

SpringBoot实现电子文件签字+合同系统

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 在现代企业中&#xff0c;合同管理和电子文件签字已成为日常运营不可或缺的一部分。为了提升效率和安全性&#xff0c;我们可以使用SpringBoot框架来实现一个电子文件签字和合同管理系统。本文将详细介绍如何…

腾讯云SDK连麦应用

音视频终端 SDK&#xff08;腾讯云视立方&#xff09;将新版连麦管理方案的多个功能集成至 腾讯云视立方控制台 > 连麦管理&#xff0c;便于用户快捷使用&#xff0c;具体分为快速上手、连麦应用、用量统计和地址生成器四个功能页面。更多连麦功能说明&#xff0c;请参见 新…

【算法刷题指南】BFS解决FloodFill算法

&#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系列 &#x1f308;Linux学习专栏&#xff1a; 南桥谈Linux &#x1f308;数据结构学习专栏&#xff1a; 数据结构杂谈 &#x1f308;数据…

Leetcode - 周赛418

目录 一&#xff0c;3309. 连接二进制表示可形成的最大数值 二&#xff0c;3310. 移除可疑的方法 三&#xff0c;3311. 构造符合图结构的二维矩阵 四&#xff0c;3312. 查询排序后的最大公约数 一&#xff0c;3309. 连接二进制表示可形成的最大数值 本题数据范围较小&#…

sklearn机器学习实战——随机森林回归与特征重要性分析全过程(附完整代码和结果图)

sklearn机器学习实战——随机森林回归与特征重要性分析全过程&#xff08;附完整代码和结果图&#xff09; 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目…

南京大学《软件分析》李越, 谭添——1. 导论

导论 主要概念: soundcompletePL领域概述 动手学习 本节无 文章目录 导论1. PL(Programming Language) 程序设计语言1.1 程序设计语言的三大研究方向1.2 与静态分析相关方向的介绍与对比静态程序分析动态软件测试形式化(formal)语义验证(verification) 2. 静态分析:2.1莱斯…

爆红网络的膨胀飞天视频,背后竟是Pika1.5 AI视频模型!

最近&#xff0c;社交网络上疯传着各种动物、建筑等物体膨胀飞上天的搞笑视频。无论是真实的还是虚构的物体&#xff0c;都在视频中被压扁、融化、膨胀&#xff0c;引发了广泛的病毒式传播。而这些有趣的效果&#xff0c;都是由Pika最新推出的1.5版本AI视频模型所制作的。 Ai …

STM32输入捕获模式详解(下篇):PWM输入捕获与PWI模式

1. 前言 在上篇文章中&#xff0c;我们详细介绍了STM32输入捕获模式的基本原理和应用方法&#xff0c;包括测频法和测周法。本文将重点探讨如何通过STM32的PWI&#xff08;PWM Input&#xff09;模式实现对PWM信号的频率和占空比测量。我们将结合具体的硬件电路&#xff0c;解…