关于前端:Web-Worker-文献综述

59次阅读

共计 17347 个字符,预计需要花费 44 分钟才能阅读完成。

作者:TAT.cnt

导语

Web Worker 作为浏览器多线程技术, 在页面内容不断丰富, 性能日趋简单的当下, 成为缓解页面卡顿, 晋升利用性能的可选计划. 但她的相貌, 暗藏在边缘试探的科普文章和不知深浅的兼容性背地; 对 JS 单线程面试题滚瓜烂熟的前端工程师, 对多线程开发有着人造的陌生感.

Web Worker 文献综述

Web Worker 作为浏览器多线程技术, 在页面内容不断丰富, 性能日趋简单的当下, 成为缓解页面卡顿, 晋升利用性能的可选计划.

但她的相貌, 暗藏在边缘试探的科普文章和不知深浅的兼容性背地; 对 JS 单线程面试题滚瓜烂熟的前端工程师, 对多线程开发有着人造的陌生感.

⇈图片起源

背景

文献综述

文献综述 (Literature Review) 是学术研究畛域一个常见概念, 写过毕业论文的同学应该还有印象. 它向读者介绍与主题无关的详细资料、动静、停顿、瞻望以及对以上方面的评述.

近期笔者关注 Web Worker, 并落地到了 大型简单前端我的项目. 开源了 Worker 通信框架 alloy-worker, 正在写实际总结文章. 其间查阅了相干材料(50+ 文章, 10+ 技术演讲), 独立写成这篇综述性文章.

次要内容

  • Worker 倒退历史
  • 主线程和多线程
  • Worker 利用场景
  • 语法和运行环境
  • Worker 通信速度
  • 浏览器兼容性
  • 调试工具用法
  • 社区配套工具
  • 业界实际回顾
  • 实际倡议和总结

倒退历史

简介

前端同学对 Web Worker 应该不生疏, 即便没有入手实际过, 应该也在社区上看过相干文章. 在介绍和应用上, 官网文档是 MDN 的 Web Workers API. 其对 Web Worker 的表述是:

Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application.

如下图所示, Web Worker 实现了多线程运行 JS 能力. 之前页面更新要先 串行 (Serial) 做 2 件事件; 应用 Worker 后, 2 件事件可 并行(Parallel) 实现.

⇈图片起源

能够直观地联想: 并行可能会 晋升执行效率 ; 运行工作拆分能 缩小页面卡顿. 前面利用场景章节将持续探讨.

技术规范

Web Worker 属于 HTML 标准, 标准文档见 Web Workers Working Draft, 有趣味的同学能够读一读. 而它并不是很新的技术, 如下图所示: 2009 年就提出了草案.

⇈图片起源

同年在 FireFox3.5 上率先实现, 能够在 using web workers: working smarter, not harder 中看到晚期的实际. 2012 年公布的 IE10 也实现了 Web Worker, 标记着支流浏览器上的全面反对. IE10 的 Web Worker 能力测试如下图所示:

⇈图片起源

在预研 Worker 计划时, 开发人员会有 兼容性顾虑. 这种顾虑的普遍存在, 次要因为业界 Worker 技术实际较少和社区推广不沉闷. 单从倒退历史看, Worker 从 2012 年起就宽泛可用; 前面兼容性章节将持续探讨.

DedicatedWorker 和 SharedWorker

Web Worker 标准中包含: DedicatedWorker 和 SharedWorker; 标准并不包含 Service Worker, 本文也不会展开讨论.

⇈图片起源

如上图所示, DedicatedWorker 简称 Worker, 其线程只能与一个页面渲染过程 (Render Process) 进行绑定和通信, 不能多 Tab 共享. DedicatedWorker 是 最早实现并最广泛支持 的 Web Worker 能力.

而 SharedWorker 能够在多个浏览器 Tab 中拜访到同一个 Worker 实例, 实现多 Tab 共享数据, 共享 webSocket 连贯等. 看起来很美妙, 但 safari 放弃了 SharedWorker 反对, 因为 webkit 引擎的技术起因. 如下图所示, 只在 safari 5~6 中短暂反对过.

⇈图片起源

社区也在探讨 是否持续反对 SharedWorker; 多 Tab 共享资源的需要倡议在 Service Worker 上寻找计划.

相比之下, DedicatedWorker 有着更广的兼容性和更多业务落地实际, 本文前面探讨中的 Worker 都是特指 DedicatedWorker.

主线程和多线程

用户应用浏览器个别会关上多个页面 (多 Tab), 古代浏览器应用独自的过程(Render Process) 渲染每个页面, 以晋升页面性能和稳定性, 并进行操作系统级别的内存隔离.

⇈图片起源

主线程(Main Thread)

页面内, 内容渲染和用户交互次要由 Render Process 中的主线程进行治理. 主线程渲染页面每一帧(Frame), 如下图所示, 会蕴含 5 个步骤: JavaScript → Style → Layout → Paint → Composite, 如果 JS 的执行批改了 DOM, 可能还会暂停 JS, 插入并执行 Style 和 Layout.

⇈图片起源

而咱们熟知的 JS 单线程和 Event Loop, 是主线程的一部分. JS 单线程执行防止了多线程开发中的简单场景 (如竞态和死锁). 但单线程的次要困扰是: 主线程同步 JS 执行耗时过久时(浏览器现实帧距离约 16ms), 会阻塞用户交互和页面渲染.

⇈图片起源

如上图所示, 长耗时工作执行时, 页面将无奈更新, 也无奈响应用户的输出 / 点击 / 滚动等操作. 如果卡死太久, 浏览器可能会抛出卡顿的提醒. 如下图所示.

  • Chrome81

  • IE11

多线程

Web Worker 会创立 操作系统级别的线程.

The Worker interface spawns real OS-level threads. — MDN

JS 多线程, 是有独立于主线程的 JS 运行环境. 如下图所示: Worker 线程有独立的内存空间, Message Queue, Event Loop, Call Stack 等, 线程间通过 postMessage 通信.

多个线程能够并发运行 JS. 相熟 JS 异步编程的同学可能会说, setTimeout / Promise.all 不就是并发吗, 我写得可溜了.

JS 单线程中的 ” 并发 ”, 精确来说是 Concurrent. 如下图所示, 运行时 只有一个函数调用栈 , 通过 Event Loop 实现不同 Task 的上下文切换(Context Switch). 这些 Task 通过 BOM API 调起其余线程为主线程工作, 但回调函数代码逻辑 仍然由 JS 串行运行.

Web Worker 是 JS 多线程运行技术, 精确来说是 Parallel. 其与 Concurrent 的区别如下图所示: Parallel 有多个函数调用栈, 每个函数调用栈能够独立运行 Task, 互不烦扰.

利用场景

探讨完主线程和多线程, 咱们能更好地了解 Worker 多线程的利用场景:

  • 能够缩小主线程卡顿.
  • 可能会带来性能晋升.

缩小卡顿

依据 Chrome 团队提出的用户感知性能模型 RAIL, 同步 JS 执行工夫不能过长. 量化来说, 播放动画时倡议小于 16ms, 用户操作响应倡议小于 100ms, 页面关上到开始出现内容倡议小于 1000ms.

逻辑异步化

缩小主线程卡顿的次要办法为异步化执行, 比方播放动画时, 将同步工作拆分为多个小于 16ms 的子工作, 而后在页面每一帧前通过 requestAnimationFrame 按计划执行一个子工作, 直到全副子工作执行结束.

⇈图片起源

拆分同步逻辑的异步计划对大部分场景有成果, 但并不是一劳永逸的银弹. 有以下几个问题:

  • 不是所有 JS 逻辑都可拆分. 比方数组排序, 树的递归查找, 图像处理算法等, 执行中须要保护以后状态, 且调用上非线性, 无奈轻易地拆分为子工作.
  • 能够拆分的逻辑难以把控粒度. 如下图所示, 拆分的子工作在高性能机器 (iphoneX) 上能够管制在 16ms 内, 但在性能落后机器 (iphone6) 上就超过了 deadline. 16ms 的用户感知工夫, 并不会因为用户手上机器的差异而变动, Google 给出的倡议是再拆小到 3-4ms.

⇈图片起源

  • 拆分的子工作并不稳固. 对同步 JS 逻辑的拆分, 须要依据业务场景寻找原子逻辑, 而原子逻辑会追随业务变动, 每次改变业务都须要去 review 原子逻辑.

Worker 一步到位

Worker 的多线程能力, 使得同步 JS 工作的拆分一步到位: 从宏观上将整个同步 JS 工作异步化. 不须要再去苦苦寻找原子逻辑, 逻辑异步化的设计上也更加简略和可保护.

这给咱们带来更多的设想空间. 如下图所示, 在浏览器主线程渲染周期内, 将可能阻塞页面渲染的 JS 运行工作 (Jank Job) 迁徙到 Worker 线程中, 进而缩小主线程的累赘, 缩短渲染距离, 缩小页面卡顿.

性能晋升

Worker 多线程并不会间接带来计算性能的晋升, 是否晋升与设施 CPU 核数和线程策略无关.

多线程与 CPU 核数

CPU 的单核 (Single Core) 和多核 (Multi Core) 离前端仿佛有点远了. 但在页面上使用多线程技术时, 核数会影响线程创立策略.

过程是操作系统 资源分配 的根本单位,线程是操作系统 调度 CPU 的根本单位. 操作系统对线程能占用的 CPU 计算资源有简单的调配策略. 如下图所示:

  • 单核多线程通过工夫切片交替执行.
  • 多核多线程可在不同核中真正并行.

Worker 线程策略

一台设施上雷同工作在各线程中运行耗时是一样的. 如下图所示: 咱们将主线程 JS 工作交给新建的 Worker 线程, 工作在 Worker 线程上运行并不会比本来主线程更快, 而线程新建耗费和通信开销使得渲染距离可能变得更久.

⇈图片起源

在单核机器上, 计算资源是内卷的 , 新建的 Worker 线程并不能为页面争取到更多的计算资源. 在多核机器上, 新建的 Worker 线程和主线程都能做运算, 页面总计算资源增多, 但对单次工作来说, 在哪个线程上运行耗时是一样的.

真正带来性能晋升的是 多核多线程并发.

如多个没有依赖关系的同步工作, 在单线程上只能串行执行, 在多核多线程中能够并行执行. 如下图 alloy-worker 的图像处理 demo 所示, 在 iMac 上运行时创立了 6 条 Worker 线程, 图像处理总工夫比主线程串行解决快了约 2000ms.

值得注意的是, 目前挪动设施的外围数无限. 最新 iPhone Max Pro 上搭载的 A13 芯片 号称 6 核, 也只有 2 个高性能核芯(2.61G), 另外 4 个是低频率的能效外围(0.58G). 所以在创立多条 Worker 线程时, 倡议辨别场景和设施.

把主线程还给 UI

Worker 的利用场景, 实质上是从主线程中剥离逻辑, 让主线程专一于 UI 渲染. 这种架构设计并非 Web 技术上的独创.

Android 和 iOS 的原生开发中, 主线程负责 UI 工作; 前端畛域热门的小程序, 实现原理上就是渲染和逻辑的齐全拆散.

本该如此.

Worker API

通信 API

如上图所示的 Worker 通信流程, Worker 通信 API 非常简单. 艰深中文教程能够参考 Web Worker 应用教程. 应用细节倡议看官网文档.

双向通信示例代码如下图所示, 双向通信只需 7 行代码.

次要流程为:

  1. 主线程调用 new Worker(url) 创立 Worker 实例, url 为 Worker JS 资源 url.
  2. 主线程调用 postMessage 发送 hello, 在 onmesssage 中监听 Worker 线程音讯.
  3. Worker 线程在 onmessage 中监听主线程音讯, 收到主线程的 hello; 通过 postMessage 回复 world.
  4. 主线程在音讯回调中收到 Worker 的 world 信息.

postMessage 会在接管线程创立一个 MessageEvent, 传递的数据增加到 event.data, 再触发该事件; MessageEvent 的回调函数进入 Message Queue, 成为 待执行的宏工作 . 因而 postMessage 程序发送 的信息, 在接管线程中会 程序执行回调函数. 而且咱们无需放心实例化 Worker 过程中 postMessage 的信息失落问题, 对此 Worker 外部机制曾经解决.

Worker 事件驱动(postMessage/onmessage) 的通信 API 尽管简洁, 但大多数场景下通信须要期待响应(相似 HTTP 申请的 Request 和 Response), 并且屡次同类型通信要匹配到各自的响应. 所以业务应用个别会封装原生 API, 如封装为 Promise 调用. 这也是笔者开发 alloy-worker 的原由之一.

运行环境

在 Worker 线程中运行 JS, 会创立 独立于主线程的 JS 运行环境, 称之为 DedicatedWorkerGlobalScope. 开发者需关注 Worker 环境和主线程环境的异同, 以及 Worker 在不同浏览器上的差别.

Worker 环境和主线程环境的异同

Worker 是无 UI 的线程, 无奈调用 UI 相干的 DOM/BOM API. Worker 具体反对的 API 可参考 MDN 的 functions and classes available to workers.

⇈图片起源

上图展现了 Worker 线程与主线程的异同点. Worker 运行环境与主线程的共同点次要包含:

  • 蕴含残缺的 JS 运行时, 反对 ECMAScript 标准定义的语言语法和内置对象.
  • 反对 XmlHttpRequest, 能独立发送网络申请与后盾交互.
  • 蕴含只读的 Location, 指向 Worker 线程执行的 script url, 可通过 url 传递参数给 Worker 环境.
  • 蕴含只读的 Navigator, 用于获取浏览器信息, 如通过 Navigator.userAgent 辨认浏览器.
  • 反对 setTimeout / setInterval 计时器, 可用于实现异步逻辑.
  • 反对 WebSocket 进行网络 I/O; 反对 IndexedDB 进行文件 I/O.

从共同点上看, Worker 线程其实很弱小, 除了利用独立线程执行重度逻辑外, 其网络 I/O 和文件 I/O 能力给业务和技术计划带来很大的设想空间.

另一方面, Worker 线程运行环境和主线程的差别点有:

  • Worker 线程没有 DOM API, 无奈新建和操作 DOM; 也无法访问到主线程的 DOM Element.
  • Worker 线程和主线程间内存独立, Worker 线程无法访问页面上的全局变量 (window, document 等) 和 JS 函数.
  • Worker 线程不能调用 alert() 或 confirm() 等 UI 相干的 BOM API.
  • Worker 线程被主线程管制, 主线程能够新建和销毁 Worker.
  • Worker 线程能够通过 self.close 自行销毁.

从差别点上看, Worker 线程无奈染指 UI, 并受主线程管制, 适宜默默干活.

Worker 在不同浏览器上的差别

各家浏览器实现 Worker 标准有差别, 比照主线程, 局部 API 性能不齐备, 如:

  • IE10 发送的 AJAX 申请没有 referer, 申请可能被后盾服务器回绝.
  • Edge18 上字符编码 / Buffer 的实现有问题.

好在这种场景并不多. 并且能够在运行时通过谬误监控发现问题, 并定位和修复(polyfill).

另一方面, 一些新增的 HTML 标准 API 只在较新的浏览器上实现, Worker 运行环境甚至主线程上没有, 应用 Worker 时需判断和兼容.

多线程同构代码

Worker 线程不反对 DOM, 这点和 Node.js 十分像. 咱们在 Node.js 上做前后端同构的 SSR 时, 常常会遇到调用 BOM/DOM API 导致的报错. 如下图所示:

在开发 Worker 前端我的项目或迁徙已有业务代码到 Worker 中时, 同构代码比例可能很高, 容易调到 BOM/DOM API. 能够通过构建变量辨别代码逻辑, 或运行时动静判断所在线程, 实现同构代码在不同线程环境下运行.

通信速度

Worker 多线程尽管实现了 JS 工作的并行运行, 也带来额定的 通信开销 . 如下图所示, 从线程 A 调用 postMessage 发送数据到线程 B onmessage 接管到数据有时间差, 这段时间差称为 通信耗费.

⇈图片起源

晋升的性能 = 并行晋升的性能 – 通信耗费的性能 . 在线程计算能力固定的状况下, 要通过多线程晋升更多性能, 须要尽量 缩小通信耗费.

而且主线程 postMessage 会占用主线程同步执行, 占用工夫与数据传输方式和数据规模相干. 要防止多线程通信导致的主线程卡顿, 需抉择适合的传输方式, 并管制每个渲染周期内的数据传输规模.

数据传输方式

咱们先来聊聊主线程和 Worker 线程的数据传输方式. 依据计算机过程模型, 主线程和 Worker 线程属于同一过程, 能够拜访和操作过程的内存空间. 但为了升高多线程并发的逻辑复杂度, 局部传输方式间接隔离了线程间的内存, 相当于默认加了锁.

通信形式有 3 种: Structured Clone, Transfer Memory 和 Shared Array Buffer.

Structured Clone

Structured Clone 是 postMessage 默认的通信形式. 如下图所示, 复制一份线程 A 的 JS Object 内存给到线程 B, 线程 B 能获取和操作新复制的内存.

Structured Clone 通过复制内存的形式简略无效地隔离不同线程内存, 防止抵触; 且传输的 Object 数据结构很灵便. 但复制过程中, 线程 A 要 同步执行 Object Serialization, 线程 B 要 同步执行 Object Deserialization; 如果 Object 规模过大, 会占用大量的线程工夫.

Transfer Memory

Transfer Memory 意为转移内存, 它不须要 Serialization/Deserialization, 能大大减少传输过程占用的线程工夫. 如下图所示 , 线程 A 将指定内存的所有权和操作权转给线程 B, 但转让后线程 A 无奈再拜访这块内存.

Transfer Memory 以失去控制权来换取高效传输 , 通过内存独占给多线程并发加锁. 但只能转让 ArrayBuffer 等大小规整的二进制(Raw Binary) 数据; 对矩阵数据 (如 RGB 图片) 比拟实用. 实际上也要思考从 JS Object 生成二进制数据的运算老本.

Shared Array Buffers

Shared Array Buffer 是共享内存, 线程 A 和线程 B 能够 同时拜访和操作 同一块内存空间. 数据都共享了, 也就没有传输什么事了.

但多个并行的线程共享内存, 会产生竞争问题(Race Conditions). 不像前 2 种传输方式默认加锁, Shared Array Buffers 把难题抛给开发者, 开发者能够用 Atomics 来保护这块共享的内存. 作为较新的传输方式, 浏览器兼容性可想而知, 目前只有 Chrome 68+ 反对.

传输方式小结

  • 全浏览器兼容的 Structured Clone 是较好的抉择, 但要思考数据传输规模, 下文咱们会具体开展.
  • Transfer Memory 的兼容性也不错(IE11+), 但数据独占和数据类型的限度, 使得它是特定场景的最优解, 不是通用解;
  • Shared Array Buffers 当下蹩脚的兼容性和线程锁的开发成本, 倡议先暗中察看.

JSON.stringify 更快?

应用 Structured Clone 传输数据时, 有个暗影始终笼罩着咱们: postMessage 前要不要对数据 JSON.stringify 一把, 据说那样更快?

2016 年的 High-performance Web Worker messages 进行了测试, 的确如此. 然而文章的测试后果也只能停留在 2016 年. 2019 年 Surma 进行新的测试: 如下图所示, 横轴上雷同的数据规模, 间接 postMessage 的传输工夫广泛比 JSON.stringify 更少.

⇈图片起源

2020 年的当下, 不须要再应用 JSON.stringify. 其一是 Structured Clone 内置的 serialize/deserialize 比 JSON.stringify 性能更高; 其二是 JSON.stringify 只适宜序列化根本数据类型, 而 Structured Clone 还反对复制其余内置数据类型(如 Map, Blob, RegExp 等, 尽管大部分利用场景只用到根本数据类型).

数据传输规模

咱们再来聊聊 Structured Clone 的数据传输规模. Structured Clone 的 serialize/deserialize 执行耗时 次要受数据对象复杂度影响 , 这很好了解, 因为 serialize/deserialize 至多要以某种形式遍历对象. 数据对象的复杂度自身难以度量, 能够用序列化后的数据规模(size) 作为参考.

2015 年的 How fast are web workers 在 中等性能手机 上进行了测试: postMessage 发送数组的通信速率为 80KB/ms, 相当于现实渲染周期 (16ms) 内发送 1300KB.

2019 年 Surma 对 postMessage 的数据传输能力进行了更深入研究, 具体见 Is postMessage slow. 高性能机器(macbook) 上的测试后果如下图所示:

⇈图片起源

其中:

  • 测试数据为嵌套层数 1 到 6 层 (payload depth, 图中纵坐标), 每层节点的子节点 1 到 6 个(payload breadth, 图中横坐标) 的对象, 数据规模从 10B 到 10MB.
  • 在 macbook 上, 10MB 的数据传递耗时 47ms, 16ms 内能够传递 1MB 级别的数据.

低性能机器(nokia2) 上的测试后果如下图所示:

⇈图片起源

其中:

  • 在 nokia2 上传输 10MB 的数据耗时 638ms, 16ms 内能够传递 10KB 级别的数据.
  • 高性能机器和低性能机器有超过 10 倍的传输效率差距.

不论用户侧的机器性能如何, 用户对晦涩的感触是统一的: 前端同学的老朋友 16ms 和 100ms. Surma 兼顾低性能机型上 postMessage 容易造成主线程卡顿, 提出的数据传输规模倡议是:

  • 如果 JS 代码外面不包含动画渲染(100ms), 数据传输规模应该放弃在 100KB 以下;
  • 如果 JS 代码外面包含动画渲染(16ms), 数据传输规模应该放弃在 10KB 以下.

笔者认为, Surma 给出的倡议偏激进, 传输规模能够再大一些.

总之, 数据传输规模并没有最佳实际. 而是充沛了解 Worker postMessage 的传输老本, 在理论利用中, 依据业务场景去评估和控制数据规模.

兼容性

兼容性是前端技术计划评估中须要关注的问题. 对 Web Worker 更是如此, 因为 Worker 的多线程能力, 要么业务场景齐全用不上; 要么一用就是重度依赖的根底能力.

兼容性还不错

从前文 Worker 的历史和 兼容性视图 上看, Worker 的兼容性应该挺好的.

如上图所示, 支流浏览器在几年前就反对 Worker.

PC 端:

  • IE10(2012/09)
  • Chrome4(2010/01)
  • Safari4(2009)
  • Firefox3.5(2009)

挪动端:

  • iOS5(2012)
  • Android4.4(2013)

可用性评估指标

应用 Worker 并不是一锤子买卖, 咱们不止关注浏览器 Worker 能力的有或没有; 也关注 Worker 能力是否齐备可用. 为此笔者设计了以下几个指标来评估 Worker 可用性:

  • 是否有 Worker 能力: 通过浏览器是否有 window.Worker 来判断.
  • 是否实例化 Worker: 通过监控 new Worker() 是否报错来判断.
  • 是否跨线程通信: 通过测试双向通信来验证, 并设置超时.
  • 首次通信耗时: 页面开始加载 Worker 脚本到首次通信实现的耗时. 该指标与 JS 资源加载时长, 同步逻辑执行耗时相干.

统计数据

有了可用性评估指标, 就能够给出量化的兼容性统计数据. 你将看到的, 是 凋谢社区上惟一一份量化数据 , 2019~2020 年某大型前端我的项目(亿级 MAU) 的统计后果(By AlloyTeam alloy-worker).

其中:

  • 有 Worker 能力的终端超过 99.91%.
  • Worker 能力齐全可用的终端达到 99.58%.
  • 而且 99.58% 到 99.91% 的差距大部分因为通信超时.

小结

可见当下浏览器曾经较好地反对 Worker, 只有对 0.09% 的不反对浏览器做好 回退策略(如展现一个 tip), Worker 能够释怀地利用到前端业务中.

调试工具用法

前端工程师对 Worker 多线程开发方式比拟生疏, 对开发中的 Worker 代码调试也是如此. 本章以 Chrome 和 IE10 为例简略介绍调试工具用法. 示例页面为 https://alloyteam.github.io/alloy-worker, 感兴趣的同学能够关上页面调试一把.

Chrome 调试

Chrome 已欠缺反对 Worker 代码调试, 开发者面板中的调试形式与主线程 JS 统一.

Console 调试

Console Panel 中能够查看页面全副的 JS 运行环境, 并通过下拉框切换调试的以后环境. 如下图所示, 其中 top 示意主线程的 JS 运行环境, alloyWorker--test 示意 Worker 线程的 JS 运行环境.

切换到 alloyWorker--test 后, 就能够在 Worker 运行环境中执行调试代码. 如下图所示, Worker 环境的全局对象为 self, 类型为 DedicatedWorkerGlobalScope.

断点调试

Worker 断点调试形式和主线程统一: 源码中增加 debugger 标识的代码地位会作为断点. 在 Sources Panel 查看页面源码时, 如下图所示, 左侧面板展现 Worker 线程的 alloy-worker.js资源; 运行到 Worker 线程断点时, 右侧的 Threads 提醒所在的运行环境是名为 alloyWorker--test 的 Worker 线程.

性能调试

应用 Performance Panel 的录制性能即可. 如下图红框所示, Performance 中也记录了 Worker 线程的运行状况.

查看内存占用

Worker 的应用场景偏差数据和运算, 开发中适时回顾 Worker 线程的内存占用, 防止内存泄露烦扰整个 Render Process. 如下图所示, 在 Memory Panel 中 alloyWorker-test 线程占用的内存为 1.2M.

IE10 调试

在比拟极其的状况下, 咱们须要到 IE10 这种老旧的浏览器上定位代码兼容性问题. 好在 IE10 也反对 Worker 源码调试. 能够参考微软官网文档, 具体步骤为:

  • F12 关上调试工具, 在 Script Panel 中, 开始是看不到 Worker 线程源码的, 点击 Start debugging, 就能看到 Worker 线程的 alloy-worker.js 源码.

  • 在 Worker 源码上打断点, 就能进行调试.

数据流调试

跨线程通信数据流是开发和调试中比较复杂的局部. 因为页面上可能有多个 Worker 实例; Worker 实例上有不同的数据类型(payload); 而且雷同类型的通信可能会屡次发动.

通过 onmessage 回调打 log 调试数据流时, 倡议增加以后 Worker 实例名称, 通信类型, 通信负载等信息. 以 alloy-worker 调试模式的 log 为例:

如上图所示:

  • 每行信息包含: 线程名称, [工夫戳, 会话 Id, 事务类型, 事务负载].
  • 绿色的向下箭头 (⬇) 示意 Worker 线程收到的信息.
  • 粉红的向上箭头 (⬆) 示意 Worker 线程收回的信息.

社区配套工具

现代化前端开发都采纳模块化的形式组织代码, 应用 Web Worker 需将模块源码构建为繁多资源(worker.js). 另一方面, Worker 原生的 postMessage/onmessage 通信 API 在应用上并不棘手, 简单场景下往往须要进行通信封装和数据约定.

因而, 开源社区提供了相干的配套工具, 主解决 2 个关键问题:

  • Worker 代码打包. 将模块化的多个文件, 打包为繁多 JS 资源.
  • Worker 通信封装. 封装多线程通信, 简化调用; 或约定通信负载的数据格式.

上面介绍社区的一些次要工具, star 数统计工夫为 2020.06.

worker-loader (1.1k star)

Webpack 官网的 Worker loader. 负责将 Worker 源码打包为单个 chunk; chunk 能够是独立文件, 或 inline 的 Blob 资源.
输入内嵌 new Worker() 的 function, 通过调用该 function 实例化 Worker.

但 worker-loader 没有提供构建后的 Worker 资源 url, 下层业务进行定制有艰难. 已有相干 issue 探讨该问题; worker-loader 也不对通信形式做额定解决.

worker-plugin (1.6k star)

GoogleChromeLabs 提供的 Webpack 构建 plugin.

作为 plugin, 反对 Worker 和 SharedWorker 的构建. 无需入侵源码, 通过解析源码中 new Workernew SharedWorker 语法, 主动实现 JS 资源的构建打包. 也提供 loader 性能: 打包资源并且返回资源 url, 这点比 worker-loader 有劣势.

comlink (6.2k star)

也来自 GoogleChromeLabs 团队, 由 Surma 开发. 基于 ES6 的 Proxy 能力, 对 postMessage 进行 RPC
(Remote Procedure Call) 封装, 将跨线程的函数调用封装为 Promise 调用.

但它不波及 Worker 资源构建打包, 须要其余配套工具. 且 Proxy 在局部浏览器中须要 polyfill, 可 polyfill 水平存疑.

workerize-loader (1.7k star)

目前社区比拟残缺, 且兼容性好的计划.

相似 worker-loader + comlink 的合体. 但不是基于 Proxy, 而在构建时依据源码 AST 提取出调用函数名称, 在另一线程内置同名函数; 封装跨线程函数为 RPC 调用.

与 workerize-loader 关联的另一个我的项目是 workerize (3.8k star). 反对手写文本函数, 外部封装为 RPC; 但手写文本函数实用性不强.

userWorker (1.8k star)

很乏味的我的项目, 将 Worker 封装为 React Hook. 基本原理是: 将传入 Hook 的函数解决为 BlobUrl 去实例化 Worker. 因为会把函数转为 BlobUrl 的字符串模式, 限度了函数不能有内部依赖, 函数体中也不能调用其余函数.

比拟适宜一次性应用的纯函数, 函数复杂度受限.

其余可参考我的项目

  • promise-worker 0.4k star.
  • greenlet 4.3k star.
  • workly 1.7k star.
  • threads.js 1.1k star, 反对 nodejs.

现有工具缺点

现有的社区工具解决了 Worker 技术利用上的一些难点, 但目前还有些有余:

  • Web Worker 并不是 100% 可用的, 社区工具并没有给出回退计划.
  • 对大规模应用的场景, 代码的组织架构和构建形式并没较好的计划.
  • 局部工具在通信数据约定上不足强束缚, 可能导致运行时意外的谬误.
  • 反对 TypeScript 源码的较少, 编辑器中的函数提醒也有阻碍.

以上有余促使笔者开源了 alloy-worker, 面向事务的高可用 Web Worker 通信框架.
更加具体的工具探讨, 请查阅 alloy-worker 的业界计划比照.

业界实际回顾

实际场景

Web Worker 作为浏览器多线程技术, 在页面内容不断丰富, 性能日趋简单的当下, 成为缓解页面卡顿, 晋升利用性能的可选计划.

2010 年

2010 年, 文章 The Basics of Web Workers 列举的 Worker 可用场景如下:

2010 年的利用场景次要波及数据处理, 文本处理, 图像 / 视频解决, 网络解决等.

当下

2018 年, 文章 Parallel programming in JavaScript using Web Workers 列举的 Worker 可用场景如下:

可见, 近年来 Worker 的场景比 2010 年更丰盛, 拓展到了 Canvas drawing(离屏渲染方面), Virtual DOM diffing(前端框架方面), indexedDB(本地存储方面), Webassembly(编译型语言方面)等.

总的来说, Worker 对页面的计算工作 / 后台任务有用武之地. 接下来笔者将分享的一些具体 case, 并进行简析.

重度计算场景

石墨表格之 Web Worker 利用实战

2017 年的文章, 十分好的实际. 在线表格排序是 CPU 密集型场景, 简单工作原子化和异步化后仍然难以打消页面卡顿. 将排序迁徙到 Worker 后, 对 2500 行数据的排序操作, Scripting 工夫从 9984ms 缩小到 3650ms .

Making TensorflowJS work faster with WebWorkers

2020 年的文章, 应用活泼的图例阐明 TF.js 在主线程运行造成的掉帧. 以实时摄像头视频的动作检测为例子, 通过 Worker 实现视频动画不卡顿(16ms 内); 动作检测耗时 50ms, 然而不阻塞视频, 也有约 15FPS.

腾讯文档 Excel 函数实际

笔者撰写文章中, 近期公布.

前端框架场景

neo — webworkers driven UI framework

2019 年开源的 Worker 驱动 前端框架. 其将前端框架的拆分为 3 个 Worker: App Worker, Data Worker 和 Vdom Worker. 主线程只须要保护 DOM 和代理 DOM 事件到 App Worker 中; Data Worker 负责进行后盾申请和托管数据 store; Vdom Worker 将模板字符串转换为虚构节点, 并对每次变动生成增量去更新.

worker-dom

Google AMP 我的项目一部分. 在 Worker 中实现 DOM 操作 API 和 DOM 事件监听, 并将 DOM 变动利用到主线程实在 DOM 上. 官网 Demo 在 Worker 中间接引入 React 并实现 render!

Angular

Angular8 CLI 反对创立 Web Worker 指令, 并将耗 CPU 计算迁徙到 Worker 中; 然而 Angular 自身并不能在 Worker 中运行. 官网 angular.io 也用 Worker 来晋升搜寻性能.

数据流场景

Off-main-thread React Redux with Performance

2019 年的文章. 将 Redux 的 action 局部迁徙到 Worker 中, 开源了我的项目 redux-in-worker.
做了 Worker Redux 的 benchmark: 和主线程相差不大(然而不卡了).

Off Main Thread Architecture with Vuex

2019 年的文章. 简略剖析 UI 线程过载和 Worker 并发能力. 对 Vue 数据流框架 Vuex 进行合成, 发现 action 能够蕴含异步操作, 适宜迁徙到 Worker. 实现了 action 的封装函数和质数生成的 demo.

可视化场景

PROXX

PROXX 是 GoogleChromeLabs 开发的在线扫雷游戏, 其 Worker 能力由 Surma 开发的 Comlink 提供. Surma 顺便开发了 Worker 版本和非 Worker 版本: 在高性能机型 Pixel3 和 MacBook 上, 两者差别不大; 但在低性能机型 Nokia2 上, 非 Worker 版本点击动作卡了 6.6s, Worker 版本点击回调须要 48ms.

图片格调解决

2013 年的文章. 应用 Worker 将图片解决为复旧色调. 在当年先进的 12 核机器上, 应用 4 个 Worker 线程后, 解决工夫从 150ms 减低到 80ms; 在当年的双核机器上, 解决工夫从 900ms 减低到 500ms.

OpenCV directly in the browser (webassembly + webworker)

2020 的文章. 基于 OpenCV 我的项目, 将我的项目编译为 webassembly, 并且在 Worker 中动静加载 opencv.js, 实现了图片的灰度解决.

大型项目

OffscreenCanvas

Chrome69+ 反对, 能将主线程 Canvas 的绘制权 transfer 给 Worker 线程的 OffscreenCanvas, 在 Worker 中绘制后渲染间接到页面上; 也反对在 Worker 中新建 Canvas 绘制图形, 通过 imagebitmap transfer 到主线程展现.

hls.js

hls 是基于 JS 实现的 HTTP 实时流媒体播放库. 其应用 Worker 用于流数据的解复用(demuxer), 应用 Transfer Memory 来最小化传输的耗费.

pdf.js

判断浏览器是否反对 Worker 能力, 有 Worker 能力时将 pdf 文件解析放在 Worker 线程中.

相干视频 / 分享 PPT

Web Workers — I like the way you work it

2016 年的分享 ppt, Pokedex.org 我的项目在 Web Worker 中进行 Virtual DOM 的更新, 显著晋升疾速滚动下的渲染效率.

The main thread is overworked & underpaid

Chrome Dev Summit 2019, 十分精彩的分享, 来自 google 的工程师 Surma. 演讲指出页面主线程工作量过大, 特地是发展中国家有大量的低性能设施. 运算在 Worker 慢一点但页面不掉帧 优于 运算在主线程快一点但卡顿.

Is postMessage slow? – HTTP 203

同样来自 Surma 的技术访谈. 次要探讨 postMessage 的性能问题. 本文在通信速度局部大量援用 Surma 的钻研.

Surma 在 Worker 畛域写了多篇文章, 并开源了 Comlink.

前端項目上 Web Worker 實踐

2019 年的演讲, 笔者前共事, 曾在 Worker 实际上严密单干. 演讲探讨 Web Worker 的应用场景; Worker 的留神点和适应多线程的代码革新; 以及实际中遇到的问题和解决方案.

Weaving Webs of Workers

2019 年的演讲, 来自 Netflix 的工程师. 总结应用 Web Worker 遇到的 4 大问题, 并通过引入社区多个配套工具逐个解决.

Web Workers: A graphical introduction

2018 年的演讲, 讲多线程和 postMessage 数据传递局部图很漂亮. 将 Web Worker 利用在他开发的 Web 钢琴弹奏器.

What the heack is the event Loop anyway

2014 年的演讲, 应用活泼的图例介绍主线程 Event Loop.

实际倡议

如上文所述, 社区已有许多 Worker 技术的利用实际. 如果你的业务也有应用 Worker 的需要, 以下是几个实际的倡议.

兴许你不须要 Worker

应用 Worker 是有老本的: Worker 线程会占用系统资源; 同构代码和异步通信会减少保护老本; 多线程编程会挑战前端仔的思维.

David 的文章指出, 迫切需要 Worker 的场景并不多, 开发者须要 思考投入效益比. 简略来说, 如果页面的某个操作会耗时, 同时不想让用户觉察(转菊花), 那就用 Worker 吧.

Worker 应该是常驻线程

尽管 Worker 标准提供了 terminate API 来完结 Worker 线程, 但线程的频繁新建会耗费资源. 大多数场景下, Worker 线程应该用作常驻的线程. 开发中优先复用常驻线程.

管制 Worker 线程数目

这也很好了解, Worker 线程在争取 CPU 计算资源时, 受限于 CPU 的外围数, 过多的线程并不能 线性地 晋升性能, 而每个 Worker 线程会有约 1M 的固有内存耗费.

了解多线程开发方式

多线程开发的思维和形式, 是个比拟大的话题. 开发者须要控制线程间的通信规模, 缩小线程间数据和状态的依赖, 尝试去理解和管制 Worker 线程.

瞻望

本文试图梳理 2020 年当下 Web Worker 技术的现状和倒退.

从现状上看, Worker 曾经广泛可用, 业界也有业务和框架上的实际, 但在配套工具上仍有有余.

从发展趋势上看, Worker 的多线程能力无望成为简单前端我的项目的标配, 在缩小 UI 线程卡顿和压迫计算机性能上有收益. 但目前国内实际较少, 一方面是业务复杂程度未涉及; 另一方面是社区短少科普和实际分享.

前端多线程开发正过后. 笔者保护的 Worker 通信框架 alloy-worker 曾经开源, 大型前端我的项目落地的文章正在路上. 鸡汤和勺子都给了, 加点老干妈, 真香!

References

  • alloy-worker

https://github.com/AlloyWorker/alloy-worker

  • Web Workers API

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API

  • Remove shared workers?

whatwg/html#315

  • Using web Workers

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

  • Web Workers Working Draft

https://www.w3.org/TR/workers/

  • using web workers: working smarter, not harder (2009 年 firefox 上的实际)

https://hacks.mozilla.org/2009/07/working-smarter-not-harder/

  • Is postMessage slow? (数据通信实验设计)

https://dassur.ma/things/is-postmessage-slow/

  • 另眼看 Web Worker (探讨异步化编程)

https://www.ithome.com.tw/voice/132997

  • The Basics of Web Workers (2010, 谈到错误处理和平安限度)

https://www.html5rocks.com/en/tutorials/workers/basics/

  • Blink Workers (Blink 框架 Worker 实现介绍)

https://docs.google.com/document/d/1i3IA3TG00rpQ7MKlpNFYUF6EfLcV01_Cv3IYG_DjF7M/edit#heading=h.7smox3ra3f6n

  • Should you be using Web Workers (配图十分棒)

https://medium.com/@david.gilbertson/should-you-should-be-using-web-workers-hint-probably-not-9b6d26dc8c6a

  • How JavaScript works

https://blog.sessionstack.com/how-javascript-works-the-building-blocks-of-web-workers-5-cases-when-you-should-use-them-a547c0757f6a

  • Parallel programming in JavaScript using Web Workers

https://itnext.io/achieving-parallelism-in-javascript-using-web-workers-8f921f2d26db

  • So you want to use a Web Worker

https://povioremote.com/blog/so-you-want-to-use-a-web-worker/

EOF

AlloyTeam 欢送优良的小伙伴退出。
简历投递: alloyteam@qq.com
详情可点击 腾讯 AlloyTeam 招募 Web 前端工程师(社招)

正文完
 0