资讯专栏INFORMATION COLUMN

精读《谈谈 Web Workers》

myeveryheart / 1389人阅读

摘要:可以将耗时任务拆解出去,降低主线程的压力,避免主线程无响应。主线程接收发消息每个实例通过接收消息,通过发送消息。收发主线程消息和主线程代码类似,在代码中,也是接收消息,这个消息来自主线程或者其它。

1 引言

本周精读的文章是 speedy-introduction-to-web-workers,是一篇 Web Workers 快速入门的文章,借精读这篇文章的机会,谈谈对 Web Workers 的理解与运用。

2 概述
就像分工,你只负责编码,而你的朋友负责设计,那你就可以专心把自己的事情做好,而且更快速的完成任务。

本文通过一个比方,描述了 Web Workers 的两大特征:

高效。

并行。

因为浏览器是单线程的,任何大量耗时的 JS 任务都会卡住界面,使浏览器无法响应任何操作,这样的用户体验非常糟糕。Web Workers 可以将耗时任务拆解出去,降低主线程的压力,避免主线程无响应。

但 CPU 资源是有限的,Web Workers 并不能增加总体运行效率,算上通信的损耗,整体计算效率会有一定的下降。
创建 Web Workers
const worker = new Worker("../src/worker.js");

上述代码中,worker 就是一个 Web Workers 实例,执行的代码是 ../src/worker.js 路径下的文件。

收发消息

Web Workers 用来执行异步脚本,只要掌握了它与主线程通信的方式,就可以在指定时机运行异步脚本,并在运行完时将结果传递给主线程。

主线程接收发 Web Workers 消息
const worker = new Worker("../src/worker.js");

worker.onmessage = e => {};

worker.postMessage("Marco!");

每个 worker 实例通过 onmessage 接收消息,通过 postMessage 发送消息。

Web Workers 收发主线程消息
self.onmessage = e => {};

self.postMessage("Marco!");

和主线程代码类似,在 Web Workers 代码中,也是 onmessage 接收消息,这个消息来自主线程或者其它 Workers。也可以通过 postMessage 发送消息。

销毁 Web Workers
worker.terminate();

文章内容就这么多,是不是有写太简单了呢!笔者结合自己的使用经验,再补充一些知识。

3 精读 对象转移(Transferable Objects)

对象转移就是将对象引用零成本转交给 Web Workers 的上下文,而不需要进行结构拷贝。

这里要解释的是,主线程与 Web Workers 之间的通信,并不是对象引用的传递,而是序列化/反序列化的过程,当对象非常庞大时,序列化和反序列化都会消耗大量计算资源,降低运行速度。

上面的图充分证明了,大对象传递,使用对象转移各项指标都优于结构拷贝。

对象转移使用方式很简单,给 postMessage 增加一个参数,把对象引用传过去即可:

var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);

浏览器兼容性也不错:Currently Chrome 17+, Firefox, Opera, Safari, IE10+。更具体内容,可以看 Transferable Objects: Lightning Fast!。

需要注意的是,对象引用转移后,原先上下文就无法访问此对象了,需要在 Web Workers 再次将对象还原到主线程上下文后,主线程才能正常访问被转交的对象。
如何不用 JS 文件创建 Web Workers

Web Workers 优势这么大,但用起来需要在同域下创建一个 JS 文件实在不方便,尤其在前后端分离做的比较彻底的团队,前端团队能控制的仅仅是一个 JS 文件。那么下面给出几个不用 JS 文件,就创建 Web Workers 的方法:

webpack 插件 - worker-loader

worker-loader 是一个 webpack 插件,可以将一个普通 JS 文件的全部依赖提取后打包并替换调用处,以 Blob 形式内联在源码中。

import Worker from "worker-loader!./file.worker.js";

const worker = new Worker();

上述代码的魔术在于,转化成下面的方式执行:

const blob = new Blob([codeFromFileWorker], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
Blob URL

第二种方式由第一种方式自然带出:如果不想用 webpack 插件,那自己通过 Blob 的方式创建也可以:

const code = `
  importScripts("https://xxx.com/xxx.js");
  self.onmessage = e => {};
`;

const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

看上去代码更轻量一些,不过问题是当遇到复杂依赖时,如果不能把依赖都转化为脚本通过 importScripts 方式引用,就无法访问到主线程环境中的包。如果真的遇到了这个问题,可以用第一种 webpack 插件的方式解决,这个插件会自动把文件所有依赖都打包进源码。

管理 postMessage 队列

为什么 postMessage 会形成队列,为什么要管理它?

首先在 Web Workers 架构设计上就必须做成队列,因为调用 postMessage 时,对应的 Web Workers 不一定完成了初始化,所以浏览器底层必须管理一个队列,在 Web Workers 初始化完毕时,依次消费,这样才能确保任何时候发出的 postMessage 都能被 Web Workers 接收到。

其次,为什么要手动维护这个队列,原因可能取决于如下几点:

业务原因,前面的 postMessage 还没来得及消费,就不要发送新的消息,或者丢弃新的消息,这时候需要通过双向通信拿到 Web Workers 的执行结果回执,手动控制队列。

性能原因,一般 Web Workers 都会被用来执行耗时的同步运算,如果运算时间比较长,那短期塞入多个消息队列是没有意义的。

如上图所示,对于每次用户输入都要进行的 SQL Parser 很耗时,及时放在 Web Workers 也可能导致将 Workers 撑爆到无响应,这是不仅要使用多 Workers 缓冲池,还要对待执行队列进行过滤,因为用户永远只关心最后一次输入的 Parser 结果。

由于 Web Workers 运算被卡住时,除了销毁 Worker 没有别的办法,而销毁 Worker 的成本比较高,不能对每一个用户输入都销毁并新建 Web Workers,所以利用 Workers 缓冲池,当缓冲池满了,新的消费队列又进来的时候,可以销毁全部 Workers 缓冲池,换一批新缓冲池重新消费用户输入。

4 总结

Web Workers 是拆解异步计算的好帮手,vscode 网页版也通过 Web Workers 异步完成代码提示和高亮,笔者有对比过,发现 Web Workers 性能提升非常明显。

管理好你的 Web Workers 消息队列,谨防同步计算让 Web Workers 失去响应!建立一个智能的消息队列,根据业务需求设计一个最好的队列消费模型吧!

5 更多讨论
讨论地址是:精读《谈谈 Web Workers》 · Issue #108 · dt-fe/weekly

如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98545.html

相关文章

  • 如何机制地回答浏览器兼容性问题

    摘要:前言有过面试经验的同学应该都被问过浏览器兼容性的问题,对于面试官的问题,常常猝不及防,因为通常他们都是这么问的。来谈谈浏览器兼容的问题吧,你对浏览器的兼容性有了解过吗,那么如何才是我们正确回答这个问题的姿势呢。 前言 有过面试经验的同学应该都被问过浏览器兼容性的问题,对于面试官的问题,常常猝不及防,因为通常他们都是这么问的。来谈谈浏览器兼容的问题吧,你对浏览器的兼容性有了解过吗,那么如...

    leanote 评论0 收藏0
  • 如何机制地回答浏览器兼容性问题

    摘要:前言有过面试经验的同学应该都被问过浏览器兼容性的问题,对于面试官的问题,常常猝不及防,因为通常他们都是这么问的。来谈谈浏览器兼容的问题吧,你对浏览器的兼容性有了解过吗,那么如何才是我们正确回答这个问题的姿势呢。 前言 有过面试经验的同学应该都被问过浏览器兼容性的问题,对于面试官的问题,常常猝不及防,因为通常他们都是这么问的。来谈谈浏览器兼容的问题吧,你对浏览器的兼容性有了解过吗,那么如...

    Rindia 评论0 收藏0
  • 精读《Monorepo 的优势》

    摘要:引言本周精读的文章是。精读总的来说,虽然拆分子仓库拆分子包是进行项目隔离的天然方案,但当仓库内容出现关联时,没有任何一种调试方式比源码放在一起更高效。前端精读帮你筛选靠谱的内容。 1. 引言 本周精读的文章是 The many Benefits of Using a Monorepo。 现在介绍 Monorepo 的文章很多,可以分为如下几类:直接介绍 Lerna API 的;介绍如何...

    xcc3641 评论0 收藏0
  • 精读《现代 js 框架存在的根本原因》

    摘要:官方规范估计很难出现现代框架的设计了,因为官方设计中前端三剑客是相互分离的方案,为了解决现阶段前端框架的问题,必须由完全接管,这几乎就是,或者支持语法的,可这与最初网页设计思路是违背的。现代前端框架正在告诉我们新的三剑客虚拟虚拟。 1 引言 深入思考为何前端需要框架,以及 web components 是否可以代替前端框架? 原文地址,建议先阅读原文,或者阅读概述。 2 概述 现在前端...

    coordinate35 评论0 收藏0

发表评论

0条评论

myeveryheart

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<