精读《谈谈 Web Workers》

栏目: 编程语言 · 发布时间: 5年前

内容简介:本周精读的文章是就像分工,你只负责编码,而你的朋友负责设计,那你就可以专心把自己的事情做好,而且更快速的完成任务。本文通过一个比方,描述了 Web Workers 的两大特征:

1 引言

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

2 概述

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

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

  1. 高效。
  2. 并行。

因为浏览器是单线程的,任何大量耗时的 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 之间的通信,并不是对象引用的传递,而是序列化/反序列化的过程 ,当对象非常庞大时,序列化和反序列化都会消耗大量计算资源,降低运行速度。

精读《谈谈 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》

如上图所示,对于每次用户输入都要进行的 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

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入理解 Flask

深入理解 Flask

[美]Jack Stouffer / 苏丹 / 电子工业出版社 / 2016-7-1 / 79.00

Flask 是一种具有平缓学习曲线和庞大社区支持的微框架,利用它可以构建大规模的web应用。学习上手Flask非常轻松,但要深入理解却并不容易。 本书从一个简单的Flask应用开始,通过解决若干实战中的问题,对一系列进阶的话题进行了探讨。书中使用MVC(模型-视图-控制器)架构对示例应用进行了转化重构,以演示如何正确地组织应用代码结构。有了可扩展性强的应用结构之后,接下来的章节使用Flask......一起来看看 《深入理解 Flask》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具