网易首页 > 网易号 > 正文 申请入驻

2023年的Web Worker项目实践

0
分享至


前言—

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'

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

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,也引用了一个lodashmap方法,都可以在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,

  1. 优化语法支持

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

import workerpool from 'workerpool'
import type TDepts from './worker-depts'

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

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

其他问题

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

总结—

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

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

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

参考资料—

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

可使用多核能力的框架: 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

《2022 中国开源开发者报告》下载

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
中央开始新一轮严查!这五类人“好日子”到头了,老百姓拍手叫好

中央开始新一轮严查!这五类人“好日子”到头了,老百姓拍手叫好

天下纵览
2024-06-16 13:30:20
网友:孙兴慜到底说了啥?王大雷调侃回复:他说他对不起中国人民

网友:孙兴慜到底说了啥?王大雷调侃回复:他说他对不起中国人民

直播吧
2024-06-16 12:48:07
刚访问中国后就捅刀子,中方非常不悦:“立即取消”!

刚访问中国后就捅刀子,中方非常不悦:“立即取消”!

占豪
2024-06-15 03:31:31
该国将拆除所有地下停车场充电站!

该国将拆除所有地下停车场充电站!

侃半仙
2024-06-16 06:56:55
欧洲杯一夜2纪录!16岁巨星起飞:6秒狂奔40米,4人拦不住

欧洲杯一夜2纪录!16岁巨星起飞:6秒狂奔40米,4人拦不住

叶青足球世界
2024-06-16 02:21:19
印度首富之子大婚在即,婚前派对只有一个字:壕!

印度首富之子大婚在即,婚前派对只有一个字:壕!

新民周刊
2024-06-16 15:44:36
上海电影节众男星状态:45岁邓超显沧桑,李治廷寸头造型阳刚帅气

上海电影节众男星状态:45岁邓超显沧桑,李治廷寸头造型阳刚帅气

扒虾侃娱
2024-06-15 21:23:43
何谓“法律面前人人平等”?美国司法界判拜登儿子和特朗普都有罪

何谓“法律面前人人平等”?美国司法界判拜登儿子和特朗普都有罪

爆角追踪
2024-06-15 22:57:15
医科大教师因抢救患儿上课迟到受处分?校方称正核实,学生称OA系统曾发处分通报

医科大教师因抢救患儿上课迟到受处分?校方称正核实,学生称OA系统曾发处分通报

上游新闻
2024-06-16 13:32:18
美媒:中国已经“出手”了,而美国感到紧张了

美媒:中国已经“出手”了,而美国感到紧张了

新时光点滴
2024-06-16 04:40:02
倒查30年后补税是个危险信号

倒查30年后补税是个危险信号

深度财线
2024-06-15 22:03:47
姜萍圆梦大学不止职教高考,江苏教育主管部门回应

姜萍圆梦大学不止职教高考,江苏教育主管部门回应

齐鲁壹点
2024-06-16 14:58:15
暴雷!比恒大还多1.14万亿,这家巨头正式进入破产清算

暴雷!比恒大还多1.14万亿,这家巨头正式进入破产清算

财经三分钟pro
2024-06-16 15:12:08
姜萍妈妈发与女儿合照,父亲做保洁一个月900元,姐姐打工赚学费

姜萍妈妈发与女儿合照,父亲做保洁一个月900元,姐姐打工赚学费

华庭讲美食
2024-06-16 10:58:39
周海媚从小就有阴阳眼,离世前曾有诡异预兆?靠撞鬼走红是真的吗

周海媚从小就有阴阳眼,离世前曾有诡异预兆?靠撞鬼走红是真的吗

洞鉴历史
2024-06-11 14:23:53
打断妻子多根肋骨,上市公司创始人被公诉!

打断妻子多根肋骨,上市公司创始人被公诉!

中国基金报
2024-06-16 00:09:20
新娘婚礼上播放AI复原已故婆婆影像:当晚爱人反复看视频 仿佛母亲“看到”他结婚了

新娘婚礼上播放AI复原已故婆婆影像:当晚爱人反复看视频 仿佛母亲“看到”他结婚了

极目新闻
2024-06-16 13:41:50
消失173天后,凯特王妃终于现身!但昨天发的近照图破绽百出,怎么回事呀?

消失173天后,凯特王妃终于现身!但昨天发的近照图破绽百出,怎么回事呀?

北美省钱快报
2024-06-16 00:17:56
突发!6月14日,“大嫂”高叶被曝出大瓜!

突发!6月14日,“大嫂”高叶被曝出大瓜!

小咪侃娱圈
2024-06-16 11:50:21
网传:焚烧电动车现场,黑烟滚滚,网友纳闷,专家学者集体沉默!

网传:焚烧电动车现场,黑烟滚滚,网友纳闷,专家学者集体沉默!

眼光很亮
2024-06-16 08:01:14
2024-06-16 17:16:49
开源中国
开源中国
每天为开发者推送最新技术资讯
6328文章数 34225关注度
往期回顾 全部

科技要闻

iPhone 16会杀死大模型APP吗?

头条要闻

G7峰会意总理向马克龙投去"死亡凝视" 视频在外网疯传

头条要闻

G7峰会意总理向马克龙投去"死亡凝视" 视频在外网疯传

体育要闻

没人永远年轻 但青春如此无敌还是离谱了些

娱乐要闻

上影节红毯:倪妮好松弛,娜扎吸睛

财经要闻

打断妻子多根肋骨 上市公司创始人被公诉

汽车要闻

售17.68万-21.68万元 极狐阿尔法S5正式上市

态度原创

教育
家居
健康
房产
数码

教育要闻

山东夏季高考选择题已阅完,整体评卷工作将于6月20日结束,6月25日公布成绩

家居要闻

空谷来音 朴素留白的侘寂之美

晚餐不吃or吃七分饱,哪种更减肥?

房产要闻

万华对面!海口今年首宗超百亩宅地,重磅挂出!

数码要闻

6 麦降噪、799 元,惠普旗下博诣 POLY 战 Free 20 耳机国行开售

无障碍浏览 进入关怀版