web worker 扫盲篇
什么是woker
官方的解释是这样的:
worker是一个对象,通过构造函数Worker创建,参数就是一个js文件的路径;文件中的js代码将运行在主线程之外的worker线程;
var jsFileURI = JS_FILE_PATH; // js文件路径 var worker = new Worker(jsFileURI);
worker运行在另一个全局上下文中(self),这个全局上下文不同于window,所以不能在woker中访问window和DOM;
该线程分为两种:dedicated worker和shared worker;dedicated worker只能被初始化它的js上下文中使用;shared worker可以在多个js上下文中使用。通常使用的worker是dedicated worker,它的工作情况可以通过chrome的调试工具查看。
为什么引入woker?
前端开发者应该知道浏览器中JS和UI公用一个线程,JS计算过程中,不能响应UI;如果遇到计算量比较大的任务,如操作图像像素时,会造成用户行为得不到响应。Web Worker 是为了解决 JavaScript 在浏览器环境中没有多线程的问题。支持 Web Worker 的浏览器会额外提供一个 JavaScript Runtime 供 Web Worker 使用。它的最佳使用场景是执行一些开销较大的数据处理或计算任务。
woker是怎么工作的?
Web Worker 使用起来非常简单,在“主线程”中执行如下操作即可创建一个 Worker 实例,通过监听 onmessage 事件获取消息,通过 postMessage 发送消息:
“主线程”和Worker 之间通过 postMessage 发送消息,通过监听 onmessage 事件来接收消息,从而实现二者的通信。
如下图所示:
核心代码如下:
主线程中代码
var worker = new Worker('worker.js'); worker.onmessage = function (e) { var data = e.data; } var messageData = { message: 'hello worker!' }; worker.postMessage(messageData);
worker.js 中的代码如下:
self.onmessage = function(e) { var messages = e.data; // e.data为{message: 'hello worker!'} var workerResult = {}; // do something ... postMessage(workerResult); }
使用woker的几个tips
(1)使用多少个worker?
遇到复杂的计算,需要开启多少worker才合适呢?一般的做法是参考navigator.hardwareConcurrency 这个属性,它表示机器支持的并行最大任务数。还有一种动态检测 Worker 数量的方法,有兴趣的话可以看:https://github.com/oftn-oswg/core-estimator。
(2)优化woker与主线程通信开销
该段主要参考百度地图技术博客(https://mp.weixin.qq.com/mp/getmasssendmsg?__biz=MzIzNDE0NjMzOQ==#wechat_webview_type=1&wechat_redirect)。
Worker 与“主线程”之间的数据传递默认是通过结构化克隆(Structured Clone)完成的。数据量较大时,克隆过程会比较耗时,这会影响 postMessage 和 onmessage 函数的执行时间。
解决的办法一是先通过 JSON.stringify 将对象序列化,接收之后再用 JSON.parse 还原。因为:stringfiy + 传递字符串的耗时 < 传递对象的耗时 。
代码如下:
// 操作像素 var imageData = context.createImageData(img.width, img.height); var work = new Worker('./cal.js'); var data = { data: imageData.data, width: imageData.width, height: imageData.height }; // 将传递的参数转换成字符串 work.postMessage(JSON.stringify(data));
还有一种避开克隆传值的方法,就是使用Transferable Objects,主要是采用二进制的存储方式,采用地址引用,解决数据交换的实时性问题;Transferable Objects支持的常用数据类型有ArrayBuffer和ImageBitmap;
使用方法如下:
// 操作像素 var imageData = context.createImageData(img.width, img.height); var work = new Worker('./cal.js'); // 转化为类型数组进行传递 var int8s = new Int8Array(imageData.data); var data = { data: int8s, width: imageData.width, height: imageData.height }; // 在postMessage方法的第二个参数中指定transferList work.postMessage(data, [data.data.buffer]);
经测试,使用arrayBuffer之后,传递数据所需的时间为1ms,极大地提高了数据传输的效率。