当前位置: 首页 > news >正文

2023 年的 Web Worker 项目实践

目录

前言

引入 Web Worker

Worker 实践

Worker 到底有多难用

类库调研

有类库加持的 worker 现状

向着舒适无感的 worker 编写前进

1. 抽取依赖,管理编译和更新:

2. 定义公共调用函数,引入所打包的依赖并串联流程:

3. 优化语法支持

4. 其他问题

总结

参考资料


前言

    Web Workers 是 2009 年就已经提案的老技术,但是在很多项目中的应用相对较少,常见一些文章讨论如何写 demo ,但很少有工程化和项目级别的实践,本文会结合 Web Workers 在京东羚珑的程序化设计项目中的实践,分享一下在当下的 2023 年,关于 worker 融入项目的一些思考和具体的实现方式,涉及到的 demo 已经放在 github 上附在文末,可供参考。

        先简单介绍下 Web Workers,它是一种可以运行在 Web 应用程序后台线程,独立于主线程之外的技术。众所周知,JavaScript 语言是单线程模型的,而通过使用 Web Workers,我们可以创造多线程环境,从而可以发挥现代计算机的多核 CPU 能力,在应对规模越来越大的 Web 程序时也有较多收益。

        Web Workers 宏观语义上包含了三种不同的 Worker:DedicatedWorker(专有worker)、 SharedWorker(共享Worker)、 ServiceWorker,本文讨论的是第一种,其他两种大家可以自行研究一下。

引入 Web Worker

当引入新技术时,通常我们会考虑的问题有:

        1、兼容性如何?

        2、使用场景在哪?

问题 1,Web Workers 是 2009 年的提案,2012 年各大浏览器已经基本支持,11 年过去了,现在使用已经完全没有问题啦

问题 2,主要考虑了以下 3 点:

  1. ·Worker API 的局限性:同源限制、无 DOM 对象、异步通信,因此适合不涉及 DOM 操作的任务

  2. ·Worker 的使用成本:创建时间 + 数据传输时间;考虑到可以预创建,可以忽略创建时间,只考虑数据传输成本,这里可参考 19 年的一个测试 Is postMessage slow[1] ,简要结论是比较乐观的,大部分设备和数据情况下速度不是瓶颈

  3. ·任务特点:需要是可并行的多任务,为了充分利用多核能力,可并行的任务数越接近 CPU 数量,收益会越高。多线程场景的收益计算,可以参考 Amdahl 公式,其中 F 是初始化所需比例,N 是可并行数:

综上结论是,可并行的计算密集型任务适合用 Worker 来做。

不过 github 上我搜罗了一圈,也发现有一些不局限于此,颇有创意的项目,供大家打开思路:

  1. ·redux 挪到了 worker 内[2]

  2. ·dom 挪到了 worker 内[3]

  3. ·可使用多核能力的框架[4]

Worker 实践

        介绍完 worker ,一个问题出现了:为什么一个兼容性良好,能够发挥并发能力的技术(听起来很有诱惑力),到现在还没有大规模使用呢?

        我理解有 2 个原因:一是暂无匹配度完美的使用场景,因此引入被搁置了;二是 worker api 设计得太难用,参考很多 demo 看,限制多配置还麻烦,让人望而却步。本文会主要着力于第二点,希望给大家的 worker 实践提供一些成熟的工程化思路。

        至于第一点理由,在如此卷的前端领域,当你手中已经有了一把好用的锤子,还找不到那颗需要砸的钉子吗?

Worker 到底有多难用

下面是一个原始 worker 的调用示例,上面是主线程文件,下面是 worker 文件:

// index.js
const worker = new Worker('./worker.js')
worker.onmessage = function (messageEvent) {console.log(messageEvent)
}
// worker.js
importScripts('constant.js')
function a() {console.log('test')
}

其中问题有:

  1. ·postMessage 传递消息的方式不适合现代编程模式,当出现多个事件时就涉及分拆解析和解决耦合问题,因此需要改造

  2. ·新建 worker 需要单独文件,因此项目内需要处理打包拆分逻辑,独立出 worker 文件

  3. ·worker 内可支持定义函数,可通过importScript 方式引入依赖文件,但是都独立于主线程文件,依赖和函数的复用都需要改造

  4. ·多线程环境必然涉及同步运行多个 worker,多 worker 的启动、复用和管理都需要自行处理

看完这么多问题,有没有感觉头很大,一个设计这样原始的 api,如何舒服的使用呢?

类库调研

        首先可以想到的就是借助成熟类库的力量,下面表格是较为常见的几款 worker 类库,其中我们可能会关注的关键能力有:

  1. ·通信是否有包装成更好用的方式,比如 promise 化或者 rpc 化

  2. ·是否可以动态创建函数——可以增加 worker 灵活性

  3. ·是否包含多 worker 的管理能力,也就是线程池

  4. ·考虑 node 的使用场景,是否可以跨端运行

        比较之下,workerpool[5] 胜出,它也是个年纪很大的库了,最早的代码提交在 6 年前,不过实践下来没有大问题,下文都会在使用它的基础上继续讨论。

有类库加持的 worker 现状

        通过使用 workerpool,我们可以在主线程文件内新建 worker;它自动处理多 worker 的管理;可以执行 worker 内定义好的函数 a;可以动态创建一个函数并传入参数,让 worker 来执行。

// index.js
import workerpool from 'workerpool'
const pool = workerpool.pool('./worker.js')
// 执行一个 worker 内定义好的函数
pool.exec('a', [1, 2]).then((res) => {console.log(res)
})
// 执行一个自定义函数
pool.exec((x, y) => {return x + y}, // 自定义函数体[1, 2], // 自定义函数参数).then((res) => {console.log(res)})
// worker.js
importScripts('constant.js')
function a() {console.log('test')
}

但是这样还不够,为了可以舒适的写代码,我们需要进一步改造。

向着舒适无感的 worker 编写前进

我们期望的目标是:

  1. ·足够灵活:可以随意编写函数,今天我想计算1+1,明天我想计算1+2,这些都可以动态编写,最好它可以直接写在主线程我自己的文件里,不需要我跑到 worker 文件里去改写;

  2. ·足够强大:我可以使用公共依赖,比如 lodash 或者是项目里已经定义好的某些公共函数。

        考虑到 workerpool 具备了动态创建函数的能力,第一点已经可以实现;而第二点关于依赖的管理,则需要自行搭建,接下来介绍搭建步骤。

1. 抽取依赖,管理编译和更新:

        新增一个依赖管理文件worker-depts.js,可按照路径作为 key 名构建一个聚合依赖对象,然后在 worker 文件内引入这份依赖

// worker-depts.js
import * as _ from 'lodash-es'
import * as math from '../math'const workerDepts = {_,'util/math': math,
}export default workerDepts
// worker.js
import workerDepts from '../util/worker/worker-depts'

2. 定义公共调用函数,引入所打包的依赖并串联流程:

    worker 内定义一个公共调用函数,注入 worker-depts 依赖,并注册在 workerpool 的方法内

// worker.js
import workerDepts from '../util/worker/worker-depts'function runWithDepts(fn: any, ...args: any) {var f = new Function('return (' + fn + ').apply(null, arguments);')return f.apply(f, [workerDepts].concat(args))
}workerpool.worker({runWithDepts,
})

主线程文件内定义相应的调用方法,入参是自定义函数体和该函数的参数列表

// index.js
import workerpool from 'workerpool'
export async function workerDraw(fn, ...args) {const pool = workerpool.pool('./worker.js')return pool.exec('runWithDepts', [String(fn)].concat(args))
}

        完成以上步骤,就可以在项目任意需要调用 worker 的位置,像下面这样。自定义函数内容,引用所需依赖(已注入在函数第一个参数),进行使用了。

        这里我们引用了一个项目内的公共函数 fibonacci,也引用了一个 lodash 的 map 方法,都可以在depts 对象上取到

// 项目内需使用worker时
const res = await workerDraw((depts, m, n) => {const { map } = depts['_']const { fibonacci } = depts['util/math']return map([m, n], (num) => fibonacci(num))},input1,input2,
)

3. 优化语法支持

        没有语法支持的依赖管理是很难用的,通过对 workerDraw 进行 ts 语法包装,可以实现在使用时的依赖提示:

import workerpool from 'workerpool'
import type TDepts from './worker-depts'export async function workerDraw<T extends any[], R>(fn: (depts: typeof TDepts, ...args: T) => Promise<R> | R, ...args: T) {const pool = workerpool.pool('./worker.js')return pool.exec('runWithDepts', [String(fn)].concat(args))
}

然后就可以在使用时获取依赖提示:

4. 其他问题

        新增了 worker 以后,出现了 window和 worker 两种运行环境,如果你恰好和我一样需要兼容 node 端运行,那么运行环境就是三种,原本我们通常判断 window 环境使用的也许是 typeof window === 'object'这样,现在不够用了,这里可以改为 globalThis 对象,它是三套环境内都存在的一个对象,通过判断globalThis.constructor.name的值,值分别是'Window' / 'DedicatedWorker'/ 'Object',从而实现环境的区分

总结

        通过使用 workerpool,添加依赖管理和构建公共 worker 调用函数,我们实现了一套按需调用,灵活强大的 worker 使用方式。

        在京东羚珑的程序化设计项目中,通过把 skia 图形绘制部分逐步改造为 worker内调用,我们实现了整体服务耗时降低 75% 的效果,收益还是非常不错的。

        文中涉及的代码示例都已放在 github[6] 上,内有 vite 和 webpack 两个完整实现版本,感兴趣的小伙伴可以 clone 下来参照着看~

参考资料


[1] Is postMessage slow: https://dassur.ma/things/is-postmessage-slow/

[2] redux 挪到了 worker 内: https://blog.axlight.com/posts/off-main-thread-react-redux-with-performance

[3] dom 挪到了 worker 内: https://github.com/ampproject/worker-dom

[4] 可使用多核能力的框架: https://github.com/neomjs/neo

[5] workerpool: https://github.com/josdejong/workerpool

[6] github: https://github.com/Silencesnow/worker-demo-2022

[7] MDN Web Workers API: https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API

[8] workerpool: https://github.com/josdejong/workerpool

[9] 前端项目上 Web Worker 实践: https://www.youtube.com/watch?v=AEpG-3XXrjk

[10] Web Worker 文献综述: https://juejin.cn/post/6854573213297410062

相关文章:

2023 年的 Web Worker 项目实践

目录 前言 引入 Web Worker Worker 实践 Worker 到底有多难用 类库调研 有类库加持的 worker 现状 向着舒适无感的 worker 编写前进 1. 抽取依赖&#xff0c;管理编译和更新&#xff1a; 2. 定义公共调用函数&#xff0c;引入所打包的依赖并串联流程&#xff1a; 3. …...

C++的最后一道坎 | 百万年薪的程序员

| 导语 C 的起源可以追溯到 40 年前&#xff0c;但它仍然是当今使用最广泛的编程语言之一&#xff0c;C发明人Bjarne Stroustrup 一开始没想到 C 会获得如此大的成功&#xff0c;他说&#xff1a;“C 的成功显然令人惊讶。我认为它的成功取决于其最初的设计目标&#xff0c;就是…...

Unity的OnOpenAsset:深入解析与实用案例

Unity OnOpenAsset 在Unity中&#xff0c;OnOpenAsset是一个非常有用的回调函数&#xff0c;它可以在用户双击资源文件时自动打开一个编辑器窗口。这个回调函数可以用于自定义资源编辑&#xff0c;提高工作效率。本文将介绍OnOpenAsset的使用方法&#xff0c;并提供三个使用例…...

【Netty】Netty 程序引导类(九)

文章目录 前言一、引导程序类二、AbstractBootStrap 抽象类三、Bootstrap 类四、ServerBootstrap 类五、引导服务器5.1、 实例化引导程序类5.2、设置 EventLoopGroup5.3、指定 Channel 类型5.4、指定 ChannelHandler5.5、设置 Channel 选项5.6、绑定端口启动服务 六、引导客户端…...

如何使用进行MQ中间件接口测试

进行MQ中间件接口测试时&#xff0c;需要按以下步骤进行&#xff1a; 1.配置测试环境 搭建MQ服务器环境&#xff0c;并确保连接配置正确&#xff0c;以及客户端SDK等相关依赖库和组件已安装并配置正确。 2.制定测试计划 测试人员需要根据具体业务场景和测试目的&#xff0c;制…...

Zebec生态进展迅速,频被BitFlow、Matryx DAO等蹭热度碰瓷

进入到 2023 年以来&#xff0c; Zebec 生态的整体发展突飞猛进&#xff0c;除了流支付协议 Zebec Protocol 不断通过收购来扩大自身流支付业务、与万事达等合作推出 Zebec Card 等在支付业务上&#xff0c;实现进展外&#xff0c;其社区驱动的Layer3 模块化链 Nautilus Chain …...

7种PCB走线方式

01电源布局布线相关 数字电路很多时候需要的电流是不连续的&#xff0c;所以对一些高速器件就会产生浪涌电流。 如果电源走线很长&#xff0c;则由于浪涌电流的存在进而会导致高频噪声&#xff0c;而此高频噪声会引入到其他信号中去。 而在高速电路中必然会存在寄生电感和寄…...

Rabbit SpringBoot高级用法

Rabbit高级用法 一、Rabbit Springboot集成1.1 引入依赖1.2 添加配置1.3 添加Config1.4 编写Consumer1.5 发送消息 二、Rabbit 高级用法2.1 消息发送前置处理器2.2 消息发送确认机制2.3 消息接收后处理器2.4 事务消息 一、Rabbit Springboot集成 1.1 引入依赖 <dependency…...

找不到vcruntime140.dll,无法继续执行代码?多种解决方法解析

找不到vcruntime140.dll,无法继续执行代码&#xff1f;当你在尝试运行某个程序时&#xff0c;突然弹出一条错误提示框&#xff0c;告诉你无法继续执行代码&#xff0c;因为找不到vcruntime140.dll。这个问题很常见&#xff0c;但是它可能会让你感到困惑和疑惑。这篇文章将详细介…...

自然语言处理实战项目8- BERT模型的搭建,训练BERT实现实体抽取识别的任务

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理实战项目8- BERT模型的搭建&#xff0c;训练BERT实现实体抽取识别的任务。BERT模型是一种用于自然语言处理的深度学习模型&#xff0c;它可以通过训练来理解单词之间的上下文关系&#xff0c;从而为下游…...

pdf怎么合并在一起?软件操作更高效

PDF格式已经成为了许多文档和表格的首选格式。然而&#xff0c;当你需要合并多个PDF文件时&#xff0c;可能会遇到一些麻烦&#xff0c;在本篇文章中&#xff0c;我们将向您介绍一种简单易用的方法来合并PDF文件。 以下是可以用来合并PDF文件的软件&#xff1a; - PDF转换器&a…...

Junit常见用法

一.Junit的含义 Junit是一种Java编程语言的单元测试框架。它提供了一些用于编写和运行测试的注释和断言方法&#xff0c;并且可以方便地执行测试并生成测试报告。Junit是开源的&#xff0c;也是广泛使用的单元测试框架之一 二.Junit项目的创建 &#xff08;1&#xff09;先创…...

c++—内存管理、智能指针、内存池

1. 内存分析诊断工具&#xff1a;valgrind&#xff1b; 2. 内存管理的两种方式&#xff1a; ①用户管理&#xff1a;自己申请的&#xff0c;自己用&#xff0c;自己回收&#xff1b;效率高&#xff0c;但容易导致内存泄漏&#xff1b; ②系统管理&#xff1a;系统自动回收垃圾…...

JAVA使用HTTP代码示例

以下是使用Java发送HTTP请求的示例代码&#xff1a; java import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class HttpExample { public static void main(String[] args) { try { …...

【网络协议详解】——电子邮件系统协议(学习笔记)

目录 &#x1f552; 1. 电子邮件系统概述&#x1f552; 2. 简单邮件传送协议SMTP&#x1f552; 3. SMTP协议的命令和响应&#x1f558; 3.1 命令&#x1f564; 3.1.1 HELO&#x1f564; 3.1.2 MAIL FROM&#x1f564; 3.1.3 RCPT TO&#x1f564; 3.1.4 DATA&#x1f564; 3.1.…...

年度发布 | MeterSphere一站式开源持续测试平台发布v2.10 LTS版本

2023年5月25日&#xff0c;MeterSphere一站式开源持续测试平台正式发布v2.10 LTS版本。这是继2022年5月发布v1.20 LTS版本后&#xff0c;MeterSphere开源项目发布的第三个LTS&#xff08;Long Term Support&#xff09;版本。MeterSphere开源项目组将对MeterSphere v2.10 LTS版…...

从 OceanBase 迁移数据到 DolphinDB

OceanBase 是一款金融级分布式关系数据库&#xff0c;具有数据强一致、高可用、高性能、在线扩展、高度兼容 SQL标准和主流关系数据库、低成本等特点&#xff0c;但是其学习成本较高&#xff0c;且缺乏金融计算函数以及流式增量计算的功能。 DolphinDB 是一款国产的高性能分布…...

淘宝商品列表数据接口(支持价格、销量排序)

淘宝商品列表数据接口是淘宝提供的一种可以获取淘宝商品信息的接口。通过该接口&#xff0c;可以获取到具有一定规则的商品信息&#xff0c;例如按照价格排序、按照销量排序等。接口返回的数据格式为JSON格式&#xff0c;可以方便地处理数据。 我们可以通过调用淘宝提供的API&…...

Android 11 版本变更总览

Android 11 版本 Android 11 总览重大隐私权变更行为变更&#xff1a;所有应用行为变更&#xff1a;以 Android 11 为目标平台的应用功能和 API 概览Intent系统广播 intent&#xff08;API 级别 30&#xff09;通用应用 intent&#xff08;API 级别 30&#xff09; Android 11 …...

传染病学模型 | Matlab实现基于SIS传染病模型模拟城市内人口的互相感染及城市人口流动所造成的传染

文章目录 效果一览基本描述模型介绍程序设计参考资料效果一览 基本描述 传染病学模型 | Matlab实现基于SIS传染病模型模拟城市内人口的互相感染及城市人口流动所造成的传染 模型介绍 SIS模型是一种基本的传染病学模型,用于描述一个人群中某种传染病的传播情况。SIS模型假设每个…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...