Web Worker 详细介绍

news2024/9/22 15:34:20

Web Worker 详细介绍

如果我们有一些处理密集型的任务,但是不想让它们在主线程上运行(那样会使浏览器/UI变慢),这时候我们可能会希望 JavaScript 可以以多线程的方式操作。

虽然 JavaScript 是单线程了,但是在浏览器中单线程不是组织我们的代码运行的唯一方法。在 HTML5 中提供了一个特性叫做 Web Worker。它与 js 语言本身是没有任何关系的,也就是说,JavaScript 当前并没有任何特性可以支持多线程运行。

就比如在浏览器中可以提供多个 JavaScript 引擎实例,每个都在自己的线程上,并允许我们在每个线程上运行不同的程序。我们的程序中分离的线程块儿中的每一个都称为一个“(Web)Worker”。这种并行机制叫做“任务并行机制”,它强调将我们的程序分割成块儿来并行运行。

在我们的主程序或者另一个 worker 中,可以使用下面的方式初始化一个 worker:

let worker = new Worker('http://xxx/worker.js');

上面代码中的 url 指向一个 js 文件,它会被加载到一个 Worker 中,然后浏览器会启动一个分离的线程,让这个文件在这个线程上作为独立的程序运行。

这种用这样的URL创建的Worker称为“专用(Dedicated)Wroker”。但与提供一个外部文件的 URL 不同的是,我们也可以通过提供一个 Blob URL(另一个 HTML5 特性)来创建一个“内联(Inline)Worker”;它实质上是一个存储在单一(二进制)值中的内联文件。

Worker 不会与我们的主程序共享任何作用域或者资源,而是通过一种事件消息机制进行通信。

Worker 对象中存在事件监听器和触发器,它允许我们监听 Worker 发出的事件及主动向 Worker 发送事件。

监听 worker 中的事件(只有 message、messageError 两个事件)

worker.addEventListener('message', function(evt) {
    // ...
})

发送事件:

worker.postMessage('message to worker');

终止 Worker,它并不会等待 Worker 去完成它剩余的操作,而是立刻停止。

worker.terminate();

在 worker 内部,消息的监听、发送都是一样的:

addEventListener( "message", function(evt){
	// evt.data
} );

postMessage( "message from worker" );

要从创建一个 Worker 的程序中立即杀死它,可以在 Worker对象上调用 terminate() 方法。突然杀掉一个 Worker 线程不会给它任何机会结束它的工作,或清理任何资源。这跟我们关闭浏览器的标签页来杀死一个页面相似。

如果我们在浏览器中有两个或多个页面(或者打开同一个页面的多个标签页),试着从同一个文件 URL 中创建Worker,实际上最终结果都是是完全分离的 Worker,并不会共享同一个 Worker。

可以在调试工具的 Sources 标签中的 threads 看到对应的线程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Web Worker 的常见用途:

  • 处理密集型的数学计算
  • 大数据集合的排序
  • 数据操作(压缩,音频分析,图像像素操作等等)
  • 高流量网络通信

使用 Blob URL

加载 Web Worker 文件的时候同样会受到同源策略的限制,对于不同源的文件或者在本地访问,会报错(Uncaught DOMException)。

但是可以 Blob URL 的方式导入:

对于使用 file 协议(本地访问)的文件来说,可以直接将 worker 文件内容先放入到 <script> 标签中,之后解析成 Blob 格式,再以这个 Blob 生成一个 URL 传递给 Worker:

<!-- 在 html 中的单独写一个 script 标签 -->
<script type="unidentify" id="worker">
    addEventListener( "message", function(evt){
	    // evt.data
    });
    postMessage( "message from worker" );
</script>

看到上面代码中的 type 值了吗,它不能被识别出来(也就是不能为下面几个值:text/javascript、text/ecmascript、application/ecmascript、application/javascript、text/vbscript),否则在加载 HTML 的时候就被直接执行。

之后通过 DOM 操作获取到 script 中的内容进行处理:

const blob = new Blob([document.getElementById('worker').textContent]);
const url = window.URL.createObjectURL(blob);
let worker = new Worker(url);

还有一种情况就是加载远程CDN文件,这时候就需要使用 ajax 或者 fetch 等技术了:

fetch('https://xxxx/worker.js')
.then((response) => response.blob())
.then((blob) => {
    const url = URL.createObjectURL(blob);
    new Worker(url);
});

Worker 环境

在一个 Worker 中,我们不能访问主程序中的任何资源,也就是不能访问主程序中的任何全局变量,同时,在 Worker 中也不存在 DOM 相关的 api 或者其他其他资源。它是一个完全分离的线程,是一个比较 “干净” 的 js 环境。

同时在 Worker 内部,可以实施网络操作(Ajax,WebSocket)和设置定时器。另外,Worker 可以访问它自己的几个重要全局变量/特性的拷贝,包括 navigator,location,JSON,和 applicationCache。内部的全局变量名为 self。

默认情况吓在 Worker 中需要通过 importScripts() 方法加载其他 js 文件,使用 import 的话会报错:

import 'foo.js'
// Uncaunght SyntaxError: Cannot use import statement outside a module

importScripts('foo.js');

这些脚本会被同步地加载,这意味着在文件完成加载和运行之前,importScripts 方法的调用会阻塞 Worker 的执行

在这种情况下我们在其他文件中定义的方法就需要保存在 self 对象或者在 Worker 文件中定义的一个一个变量中,才能在 Worker 方法中使用:

// worker
var global = {};
// 因为是同步加载的,因此定义的变量最好写在文件开头
importScripts('foo.js');

// foo.js
global.foo = 'bar'

为了支持加载模块,HTML标准的开发人员为 Worker 这些构造函数添加了第二个参数,第二个参数是一个具有type属性的对象,其默认值为 “classic”,可以设置成 module 支持在 Worker 中加载模块:

// 主程序
const worker = new Worker('./worker.js', { type: 'module'});

// worker.js
import { foo } from 'foo.js'

// foo.js
export const foo = 'bar';

数据传输

在 Web Worker 常见用途中,基本都有一个共同性质,就是它们使用事件机制来传递大量的信息。

在 Worker 的早期,将所有数据序列化为字符串是唯一的选择。除了对大数据量进行序列化时速度变慢以外,另外一个主要缺点是,数据是被拷贝的(而不是共享的),这意味着内存用量翻了一倍(以及在后续垃圾回收上的流失)。

现在有其他更好的选择:

  1. structuredClone():“结构化克隆算法(Structured Cloning Algorithm)”,用于拷贝/复制这个对象。这个算法相当精巧,甚至可以处理带有循环引用的对象复制。但用这种方式我们依然面对着内存用量的翻倍。
const obj = {
    key: 'value'
}

const clone = structuredClone(obj);
  1. 可转移对象:对大的数据集合而言这是一个更好的选择,它使对象的“所有权”被传送,而对象本身没动。一旦我们传送一个对象给 Worker,它在原来的位置就空了出来或者不可访问 —— 这消除了共享作用域的多线程编程中的难题。当然,所有权的传送可以双向进行。选择使用可转移对象不需要我们做太多;任何实现了Transferable 接口的数据结构都将自动地以这种方式传递。

在 JavaScript 中,可转移对象(Transferable Objects)是指 ArrayBuffer 和 MessagePort 等类型的对象,它们可以在主线程和 Web Worker 线程之间相互传递,同时还可以实现零拷贝内存共享,提高性能。这是由于可转移对象具有两个特点:

可共享:可转移对象本身没有所有权,可以在多个线程之间共享,实现零拷贝内存共享。
可转移:调用 Transferable API 时,可转移对象会从发送方(发送线程)转移到接收方(接收线程),不再存在于原始线程中,因此可以避免内存拷贝和分配等开销。

要注意的是,使用可转移对象时必须小心处理,因为一旦对象被转移,原线程将不再拥有该对象的所有权,因此在发送线程中不能再访问该对象。此外,在接收线程中使用可转移对象时,也需要根据需求进行显式释放,否则可能会导致内存泄漏和其他问题。

可转移对象:

  • ArrayBuffer
  • MessagePort
  • ReadableStream
  • WritableStream
  • TransformStream
  • WebTransportReceiveStream
  • WebTransportSendStream
  • AudioData
  • ImageBitmap
  • VideoFrame
  • OffscreenCanvas
  • RTCDataChannel
const original = new Uint8Array(1024);
const clone = structuredClone(original);
console.log(original.byteLength); // 1024
console.log(clone.byteLength); // 1024

original[0] = 1;
console.log(clone[0]); // 0

// 直接转移 Uint8Array 实例会报错,因为它不是可转移的对象
// const transferred = structuredClone(original, {transfer: [original]});

// 转移 Uint8Array.buffer.
const transferred = structuredClone(original, { transfer: [original.buffer] });
console.log(transferred.byteLength); // 1024
console.log(transferred[0]); // 1

// 在转移之后原来的 Uint8Array.buffer 不能再使用
console.log(original.byteLength); // 0

Worker 共享

在浏览器中可以通过多个标签页加载同一个页面,那么这时候能不能只创建一个单独的中心化 Worker 来让多个页面实例共享同一个 Worker,降低系统资源的使用量?

浏览器提供了一个 SharedWorker 类可以实现这种需求:

let worker = new SharedWorker('http://xxx/worker.js')

因为一个共享 Worker 可以连接或被连接到多个程序实例或网页,Worker 需要一个方法来知道消息来自哪个程序。这种唯一的标识称为“端口(port)”,所以调用端程序必须使用 Worker 的 port 对象来通信:

worker.port.addEventListener('message', function(evt) {
    // ...
})

worker.port.postMessage('message to worker');

当然,最重要的是端口的连接必须要先初始化:

worker.port.start();

在共享 Worker 内部,有一个额外的事件必须被处理:“connect”。这个事件为这个特定的连接提供端口对象。保持多个分离的连接最简单的方法是在 port 上使用闭包,就像下面展示的那样,同时在 “connect” 事件的处理器内部定义这个连接的事件监听与传送:

var port;
addEventListener( "connect", function(evt){
	// 为这个连接分配的端口
	port = evt.ports[0];
	port.addEventListener( "message", function(evt){
		// ..
		port.postMessage( .. );

		// ..
	} );
	// 初始化端口连接
	port.start();
});

// 关闭线程
port.close();

这方面最常见的资源限制是 websocket 链接,因为浏览器限制同时连接到一个服务器的连接数量。

polyfill

我们的代码可能运行在旧版本的浏览器中,它们可能不支持 Worker,那么就需要一个 polyfill。

请添加图片描述

JS 的异步能力(不是并行机制)来自于事件轮询队列,所以我们可以用计时器(setTimeout(…)等等)来强制模拟的 Worker 是异步的。然后只需要提供 Worker API就行了。

这里有一份 polyfill 列表https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#web-workers

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1950827.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超声波眼镜清洗机哪款好用又实惠?4款高评分眼镜清洗机机型深度测评

眼镜党都知道超声波清洗机吧&#xff0c;每次眼镜脏了&#xff0c;去眼镜店清洗&#xff0c;店员用的就是超声波清洗机。利用超声波的原理&#xff0c;这种清洗机可以深入物品内部进行清洁&#xff0c;效果非常出色。相比手工清洗&#xff0c;超声波清洗机能在清洁过程中保护镜…

远程项目调试-informer2020

informer2020 Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting(原文&#xff09;Informer 是一个基于Transformer的模型&#xff0c;是为了应对长依赖关系而开发的。本文的主要主题是序列预测。序列预测可以在任何具有不断变化的数据的地方…

做短视频素材哪里找?去哪里下载?自媒体下载素材网站分享

自媒体视频创作&#xff1a;高质量素材网站大公开&#xff01; 大家好&#xff0c;我是一名热情的短视频创作者。今天&#xff0c;我要与大家分享一些寻找优质视频素材的秘诀。无论是新手还是老手&#xff0c;这些建议都能帮助你的视频在众多平台中脱颖而出&#xff0c;吸引更…

系统移植(四)u-boot移植 ② basic版本

文章目录 一、u-boot移植&#xff08;一&#xff09;生成u-boot源码1. 配置交叉编译器&#xff0c;修改u-boot源码目录下的MAKEFILE文件4. 执行make <board_name>_defconfig命令&#xff0c;配置u-boot源码5. make menuconfig---图形化界面配置6. 根据DK1板子的设备树文件…

ISP 代理提供商:互联网安全的关键参与者

简介&#xff1a;互联网安全的演变态势 互联网改变了我们互动、工作和开展业务的方式&#xff0c;但也带来了与安全性和可访问性相关的重大挑战。在这个数字时代&#xff0c;互联网服务提供商 (ISP) 代理提供商在解决这些问题方面发挥着关键作用。他们提供的基本服务不仅可以增…

PDF解锁网站

https://smallpdf.com/cn/unlock-pdfhttps://smallpdf.com/cn/unlock-pdfhttps://www.freemypdf.comhttps://www.freemypdf.com

LVGL使用上一个不方便的问题记录

slider这个控件&#xff0c;应该画在蓝色框的里面&#xff0c;源码实现将中心画在蓝色框上&#xff0c;导致总会有两边超出的情况出现&#xff0c;真想修改源码&#xff0c;作者不知道咋想的&#xff1f;&#xff1f;&#xff1f;

vue3 Router 点击index中的按钮,查看相应的详情信息,并且传递id,及其路由的定义方法。

1、路由的定义 结构如下: 2、路由定义代码&#xff1a; {path: tabs,name: TabsDemo,component: () > import(/views/demo/feat/tabs/index.vue),meta: {title: t(routes.demo.feat.tabs),hideChildrenInMenu: true,},children: [{path: detail/:id,name: TabDetail,compon…

封装和桥接Unity 协程体系

简介 协程&#xff08;Coroutine&#xff09;在C#中是一种特殊的函数&#xff0c;它允许开发者编写可以暂停执行并在未来某个时刻恢复执行的代码块。协程通常用于实现异步操作&#xff0c;如延时执行、等待某个事件发生、或者分段执行复杂的任务。在Unity游戏引擎中&#xff0c…

Cuda编程模型中常见的错误检测方法

Cuda编程模型中常见的错误检测方法 1 CUDA错误检测简介2 直接嵌入检测函数2.1 检测函数介绍2.2 使用示例 3 封装在.cuh头文件中嵌入3.1 创建 error.cuh 头文件3.2 在 CUDA 程序中包含 error.cuh 并调用 CHECK 宏3.3 使用示例 1 CUDA错误检测简介 CUDA编程模型中的错误检测是确…

【C++】选择结构案例-三只小猪称体重

案例问题 假设有三只小猪A、B、C&#xff0c;在输入三者体重后希望能输出他们各自的体重并测出谁最重 思路 先让A与B相比较&#xff0c;如果A重&#xff0c;则让A和C相比较&#xff0c;如果A重则输出A最重&#xff0c;否则输出C最重 在最开始的条件&#xff08;AB相比较&am…

JQuery简单实现ul li点击菜单项被选中的菜单项保持高亮状态(导航ul li点击切换样式)

效果&#xff1a; JS&#xff1a; $(function () {//遍历list&#xff08;一般为ul li&#xff09;$("#menu a").each(function () {//给当前项添加点击事件&#xff08;点击后切换样式&#xff09;$(this).bind(click,function () {// 移除其他所有项的active类$(&…

Sokit(TCP/UDP调试工具)

下载&#xff1a;http://www.winwin7.com/soft/56522.html#xiazai Sokit中文版是一款免费开源的TCP / UDP 测试&#xff08;调试&#xff09;工具&#xff0c;它主要可以用于接收和发送TCP/UDP数据包&#xff0c;让你更深的了解网络状况&#xff0c;能够有效地接收、发送、转…

Linux中的进程1

进程的概念 程序&#xff1a;二进制文件 进程&#xff1a;启动的程序 所有的数据都在内存中 需要占据更多的系统资源 cpu&#xff0c;物理内存&#xff08;RAM&#xff09; 并行和并发 并发&#xff1a;在操作系统中&#xff0c;是指一个时间段中有几个程序都处于已启动…

干货讲解 | 在线教育行业如何搭建帮助中心

引言 随着互联网技术的飞速发展&#xff0c;在线教育已成为教育领域不可或缺的一部分&#xff0c;它打破了传统教育的时空限制&#xff0c;让知识传播更加高效、便捷。然而&#xff0c;在享受在线教育带来的便利时&#xff0c;用户也面临着操作复杂、功能理解不透彻、遇到问题…

Java语言程序设计——篇七(2)

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 封装性与多态 封装性与访问修饰符类的访问权限类成员的访问权限 &#x1f320;防止类扩展和方法覆盖实战演练 抽象类实战演练 对象转换实战演练…

github的Codespaces是什么

目录 github的Codespaces是什么 一、定义与功能 二、特点与优势 三、工作原理 四、使用场景与限制 github的Codespaces是什么 GitHub的Codespaces是一个基于云的即时开发环境,它利用容器技术为开发者提供一个完全配置好的开发环境,以便他们能够直接在浏览器或通过Visua…

《中国数据库前世今生》观影——认识1980年起步阶段

引出 中国数据库的前世今生观影——认识1980年的起步阶段 20 世纪 60 年代国外就有了商业数据库&#xff0c;20 世纪 80 年代我国才有了第一批数据库专业人才。不要小看这 20 年的差距&#xff0c;它可能需要几代数据库人用一生去追。2024 年了&#xff0c;中国跨过数据库这座大…

【Android】Fragment的数据传递

碎片和活动之间的通信 Activity向Fragment 通过方法传递 构造方法 将碎片动态地加载到活动当中&#xff0c;先得到一个碎片&#xff0c;再将其放到活动当中。就想到碎片的替代方法&#xff0c;将我们所要传输的数据直接放到新创建的碎片里面&#xff0c;替换到原来的碎片。…

【C++】C++应用案例-大整数相加

实际应用中&#xff0c;有时会遇到非常大的整数&#xff0c;可能会超过long、甚至long long的范围。这时就需要用不限长度的字符串保存数据&#xff0c;然后进行计算。 最简单的需求就是“大整数相加”&#xff0c;即给定两个字符串形式的非负大整数 num1 和num2 &#xff0c;计…