一、关于Web Worker工作线程

  HTML5几个优势特性里,就包括了Web Worker,这货可以了解为多线程,正常形况下,浏览器执行某段程序的时候会阻塞直到运行结束后在恢复到正常状态,而HTML5的Web Worker就是为了解决这个问题。

  允许JavaScript创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。
  所以它能解决两个问题:一、解决程序阻塞问题;二、提升效率。

二、示例

  常测试效率最常用的无非就是fibonacci了,我们也来个fibonacci性能测试。

var start = (new Date()).getTime();
var fibonacci =function(n) {
return n<2 ? n : arguments.callee(n-1) + arguments.callee(n-2);
};
fibonacci(38);
console.log((new Date()).getTime() - start);
//console.log打出 4792

  每台电脑性能不一样,浏览器不一样可能会有变化,38执行需要接近5s,两次就差不多10s。

    fibonacci(38);
    fibonacci(38);
    // 两次执行需要 9694

  我们引入Web Worker,提升效率,缩短运算时差

worker.js

var fibonacci =function(n) {
return n<2 ? n : arguments.callee(n-1) + arguments.callee(n-2);
}; self.addEventListener('message', function(event) {
self.postMessage(fibonacci(event.data))
}, false);

主页面中的js

//第二种
var start = (new Date()).getTime();
// 实例化一个worker实例,参数必须是一篇JavaScript脚本
var worker = new Worker('worker.js');
// 监听worker通信
worker.addEventListener('message', function(event) {
console.log('Worker result: ' + ((new Date()).getTime() - start));
}, false); // 向worker post数据
worker.postMessage(38); var fibonacci =function(n) {
return n<2 ? n : arguments.callee(n-1) + arguments.callee(n-2);
}; // 主页面仅剩一个,另外一个已经转移到worker里执行了
setTimeout(function(){
fibonacci(38);
//console.log((new Date()).getTime() - start);
alert((new Date()).getTime() - start)
}, 100);

这时候的执行结果相当于执行两次用了一次的时间。

三、封装

但如果要连续执行好几个,可不能这样:

worker.postMessage(38);
worker.postMessage(38);
worker.postMessage(38);

因为只是new了一个Worker,所以它会顺序执行:

script.js:26 5369
script.js:9 Worker result: 5374
script.js:9 Worker result: 9960
script.js:9 Worker result: 14557

封装:

我们可以同时new多个

//第三种
var start = (new Date()).getTime();
var fibonacci =function(n) {
// 实例化一个worker实例,参数必须是一篇JavaScript脚本
var worker = new Worker('worker.js');
// 监听worker通信
worker.addEventListener('message', function(event) {
console.log('Worker result: ' + ((new Date()).getTime() - start));
}, false);
// 向worker post数据
worker.postMessage(n);
};
fibonacci(38)
fibonacci(38)
fibonacci(38)
fibonacci(38)

执行4次的结果:

script.js:11 Worker result: 9323
script.js:11 Worker result: 9335
script.js:11 Worker result: 9340
script.js:11 Worker result: 9350
可见实例越多,单个执行效率就越高,因为new一个Worker也是需要耗费时间的,但即使这样也比在浏览器里阻塞顺序执行效率更高。

四、跨域与脚本引入

1、Worker引入必须是本域

Worker在实例化的时候必须要传入一个脚本URL,而且必须是在本域下,否则会报跨域错误:本域:http://localhost:63342/

var worker = new Worker('http://localhost/worker.js');

报安全错误:
  Uncaught SecurityError: Failed to construct 'Worker': Script at 'http://localhost/worker.js' cannot be accessed from origin 'http://localhost:63342'./

2、Worker中代码通过importScripts方法引入任何域下的脚本

  可以在Worker里通过importScripts方法引入任何域下的脚本,就如同HTML里的script标签一样

  worker里引入它

    self.importScripts('http://localhost/script.js');

    console.log: Hello world! from http://localhost/script.js

3、同步异步

  new Worker 与 importScripts是异步的

  importScripts加载是同步的

    self.importScripts('/script1');
    self.importScripts('/script2');

五、优缺点

5.1、支持api,在worker线程中,可以获得下列对象

1)     navigator对象

2)     location对象,只读

3)     XMLHttpRequest对象,能发出Ajax请求

4)     setTimeout/setInterval方法

5)     Application Cache

6)     通过importScripts()方法加载其他脚本,在worker里载入外部js脚本

7)     创建新的Web Worker

8)   addEventListener/postMessage,有了它们才能与主页互相通信【代码中可以直接使用onmessage

5.2、worker线程不能获得下列对象

1)     DOM对象

2)     window对象

3)     document对象

4)     parent对象

上述的规范,限制了在worker线程中获得主线程页面相关对象的能力,所以在worker线程中,不能进行dom元素的更新。

5.3、用途

1)  Web Worker带来后台计算能力

更新数据和对象状态”的耗时部分交由Web Worker执行,提升页面性能。

2)  使用专用线程进行数学运算

Web Worker最简单的应用就是用来做后台计算,而这种计算并不会中断前台用户的操作

3)  图像处理

通过使用从<canvas>或者<video>元素中获取的数据,可以把图像分割成几个不同的区域并且把它们推送给并行的不同Workers来做计算

4)  大量数据的检索

当需要在调用 ajax后处理大量的数据,如果处理这些数据所需的时间长短非常重要,可以在Web Worker中来做这些,避免冻结UI线程。

5)  背景数据分析

Angular1最被大家诟病就是它的脏检查机制,当scope的数据量过多时会严重影响性能。而Angular2正是借助WebWorker来把繁重的计算工作移入辅助线程,让界面线程不受影响。

六、原理流程

1、分类

  Web Worker 规范中定义了两类工作线程,分别是专用线程Dedicated Worker和共享线程 Shared Worker,其中,Dedicated Worker只能为一个页面所使用,而Shared Worker则可以被多个页面所共享,本文示例为专用线程Dedicated Worker。

2、详细说明

使用Dedicated Worker的主页面代码main.js

var worker = new Worker("task.js");
worker.postMessage(
{
id:1,
msg:'Hello World'
}
);
worker.onmessage=function(message){
var data = message.data;
console.log(JSON.stringify(data));
worker.terminate();
};
worker.onerror=function(error){
console.log(error.filename,error.lineno,error.message);
}

Dedicated Worker所执行的代码task.js

onmessage = function(message){
var data=message.data;
data.msg = 'Hi from task.js';
postMessage(data);
}

在main.js代码中,首先通过调用构造函数,传入了worker脚本文件名,新建了一个worker对象,这一对象是新创建的工作线程在主线程的引用。随后调用worker.postMessage()方法,与新创建的工作线程通信,这里传入了一个json对象。随后分别定义了worker对象的onmessage事件和onerror事件的回调处理函数,当woker线程返回数据时,onmessage回调函数执行,数据封装在message参数的data属性中,调用 worker 的 terminate()方法可以终止worker线程的运行;当worker线程执行出错时,onerror回调函数执行,error参数中封装了错误对象的文件名、出错行号和具体错误信息。

在task.js代码中,定义了onmessage事件处理函数,由主线程传入的数据,封装在message对象的data属性中,数据处理完成后,通过postMessage方法完成与主线程通信。在工作线程代码中,onmessage事件和postMessage方法在其全局作用域可以访问。

2、执行流程

  

1)     worker线程的创建的是异步的

代码执行到"var worker = new Worker(task.js')“时,在内核中构造WebCore::JSWorker对象(JSBbindings层)以及对应的WebCore::Worker对象(WebCore模块),根据初始化的url地址"task.js"发起异步加载的流程;主线程代码不会阻塞在这里等待worker线程去加载、执行指定的脚本文件,而是会立即向下继续执行后面代码。

2)     postMessage消息交互由内核调度

main.js中,在创建woker线程后,立即调用了postMessage方法传递了数据,在worker线程还没创建完成时,main.js中发出的消息,会先存储在一个临时消息队列中,当异步创建worker线程完成,临时消息队列中的消息数据复制到woker对应的WorkerRunLoop的消息队列中,worker线程开始处理消息。在经过一轮消息来回后,继续通信时, 这个时候因为worker线程已经创建,所以消息会直接添加到WorkerRunLoop的消息队列中;

1.3 worker线程数据通讯方式

主线程与子线程数据通信方式有多种,通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是地址,子线程对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给子线程,后者再将它还原。

主线程与子线程之间也可以交换二进制数据,比如File、Blob、ArrayBuffer等对象,也可以在线程之间发送。但是,用拷贝方式发送二进制数据,会造成性能问题。比如,主线程向子线程发送一个50MB文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,转移后主线程无法再使用这些数据,这是为了防止出现多个线程同时修改数据的问题,这种转移数据的方法,叫做Transferable Objects。

// Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

七、使用woker的几个tips

(1)使用多少个worker?

确定要使用 Web Worker 之后,确定多少个 Worker 同时工作就是下面需要考虑的问题,数量少了发挥不出并行处理的优势,数量多了有可能导致 Worker 处理速度变慢。

一般的做法是读取 navigator.hardwareConcurrency 这个属性,它表示机器最多可并行执行的任务数量,如果取不到这个值,可以给一个默认值,例如4。还有一种动态检测 Worker 数量的方法,有兴趣的话可以看:https://github.com/oftn-oswg/core-estimator

有人可能会问,假设一个机器的最大并行数是8,那么是不是只能创建7个 Worker,留一个给“主线程”使用?我的建议是要看的你的应用的实际情况,通常来说页面的“主线程”不会长时间执行操作,大部分时间都处于空闲状态,那么这时候 Worker 数量完全可以取8。通过 Chrome Dev Tools 的 Timeline(新版叫做 Performance)工具可以查看每个 Worker 的工作情况,确定是否影响“主线程”工作。

(2)、优化woker与主线程通信开销

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,极大地提高了数据传输的效率。

参看地址:

https://yq.aliyun.com/ziliao/25009

https://www.cnblogs.com/GongQi/p/4991380.html

http://mp.weixin.qq.com/s/DORzY-Rgts7RAcfrbUeiDg

003-Web Worker工作线程的更多相关文章

  1. web workers工作线程

    web worker工作线程是Html5里面提出来的一个新api,对于JavaScript我们的印象是单线程执行,如果运行复杂运算的时候,页面可能就会失去响应,是运行在后台的javascript,独立 ...

  2. 一个简单的HTML5 Web Worker 多线程与线程池应用

    笔者最近对项目进行优化,顺带就改了些东西,先把请求方式优化了,使用到了web worker.发现目前还没有太多对web worker实际使用进行介绍使用的文章,大多是一些API类的讲解,除了涉及到一些 ...

  3. Web Worker模拟抢票

    web worker工作原理图: 抢票系统思维导图: 思路:五个人(5个div窗口模拟)同时进行抢票,有百分之十的几率可以抢到票,抢到票后对应的窗口(即随机生成的数大于等于0小于9的情况)会编程天蓝色 ...

  4. JS线程模型&Web Worker

    js线程模型 客户端javascript是单线程,浏览器无法同时运行两个事件处理程序 设计为单线程的理论是,客户端的javascript函数必须不能运行太长时间,否则会导致web浏览器无法对用户输入做 ...

  5. web worker技术-js新线程

    web worker的小例子,用来入门很合适,建议启动服务来开发.可以使用node的anywhere. <!DOCTYPE html> <html lang="en&quo ...

  6. 进程,线程,Event Loop(事件循环),Web Worker

    线程,是程序执行流的最小单位.线程可与同属一个进程的其他线程共享所拥有的全部资源,同一进程中的多个线程之间可以并发执行.线程有就绪,阻塞,运行三种基本状态. 阮一峰大神针对进程和线程的类比,很是形象: ...

  7. Web Worker javascript多线程编程(一)

    什么是Web Worker? web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验. 一般来说Javascript ...

  8. Web Worker

    写在前面 众所周知,JavaScript是单线程的,JS和UI更新共享同一个进程的部分原因是它们之间互访频繁,但由于共享同一个进程也就会造成js代码在运行的时候用户点击界面元素而没有任何响应这样的情况 ...

  9. HTML5新特性之Web Worker

    1.概述 JavaScript语言采用的是单线程模型,也就是说,所有任务排成一个队列,一次只能做一件事.随着电脑计算能力的增强,这一点带来很大的不便,无法充分发挥JavaScript的潜能.龙其考虑到 ...

随机推荐

  1. 洛谷 - P1346 - 电车 - Dijkstra/01BFS

    https://www.luogu.org/problem/P1346 使用最短路之前居然忘记清空了. #include<bits/stdc++.h> using namespace st ...

  2. es6 async和await

    es7 async和await ,作为genertor函数语法糖,在使用上比generator函数方便的,Generator 函数就是一个封装的异步任务,或者说是异步任务的容器.异步操作需要暂停的地方 ...

  3. python3中编码与解码的问题

    python3中编码与解码的问题 ASCII .Unicode.UTF-8 ASCII 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit)有0和1两种状态,因此 ...

  4. 2019-10-31-VisualStudio-2019-新特性

    title author date CreateTime categories VisualStudio 2019 新特性 lindexi 2019-10-31 08:48:27 +0800 2019 ...

  5. 关于tomcat NoClassDefDoundErr异常的记录

    在做DRP项目的时候,copy了drp1.3,粘贴重命名成drp1.4,把drp1.4加入到tomcat中,发现drp1.4中新加的jsp可以正常运行,而从1.3那copy来的不能运行,抛出NoCla ...

  6. MongoDB入门_MongoDB安装与配置

    MongoDB运行环境 MongoDB环境:CentOS-6.7-i386 MongoDB版本:MongoDB 2.6.5 ssh工具:xshell 文本编辑工具:vim与editplus++ 编译M ...

  7. 十一、Boostrap-X-editable

    一.官网 http://vitalets.github.io/x-editable/index.html 二.实践 在jQuery中ajax配置项中的使用type与method的区别: type 和m ...

  8. PAT Advanced 1036 Boys vs Girls (25 分)

    This time you are asked to tell the difference between the lowest grade of all the male students and ...

  9. DDD领域驱动设计初探(三):仓储Repository(下)

    前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...

  10. c++ 创建线程用CreateThread后,线程直接就开始执行了吗

    //CreateThread函数的参数原型如下 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD SIZE_T ...