003-Web Worker工作线程
一、关于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工作线程的更多相关文章
- web workers工作线程
web worker工作线程是Html5里面提出来的一个新api,对于JavaScript我们的印象是单线程执行,如果运行复杂运算的时候,页面可能就会失去响应,是运行在后台的javascript,独立 ...
- 一个简单的HTML5 Web Worker 多线程与线程池应用
笔者最近对项目进行优化,顺带就改了些东西,先把请求方式优化了,使用到了web worker.发现目前还没有太多对web worker实际使用进行介绍使用的文章,大多是一些API类的讲解,除了涉及到一些 ...
- Web Worker模拟抢票
web worker工作原理图: 抢票系统思维导图: 思路:五个人(5个div窗口模拟)同时进行抢票,有百分之十的几率可以抢到票,抢到票后对应的窗口(即随机生成的数大于等于0小于9的情况)会编程天蓝色 ...
- JS线程模型&Web Worker
js线程模型 客户端javascript是单线程,浏览器无法同时运行两个事件处理程序 设计为单线程的理论是,客户端的javascript函数必须不能运行太长时间,否则会导致web浏览器无法对用户输入做 ...
- web worker技术-js新线程
web worker的小例子,用来入门很合适,建议启动服务来开发.可以使用node的anywhere. <!DOCTYPE html> <html lang="en&quo ...
- 进程,线程,Event Loop(事件循环),Web Worker
线程,是程序执行流的最小单位.线程可与同属一个进程的其他线程共享所拥有的全部资源,同一进程中的多个线程之间可以并发执行.线程有就绪,阻塞,运行三种基本状态. 阮一峰大神针对进程和线程的类比,很是形象: ...
- Web Worker javascript多线程编程(一)
什么是Web Worker? web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验. 一般来说Javascript ...
- Web Worker
写在前面 众所周知,JavaScript是单线程的,JS和UI更新共享同一个进程的部分原因是它们之间互访频繁,但由于共享同一个进程也就会造成js代码在运行的时候用户点击界面元素而没有任何响应这样的情况 ...
- HTML5新特性之Web Worker
1.概述 JavaScript语言采用的是单线程模型,也就是说,所有任务排成一个队列,一次只能做一件事.随着电脑计算能力的增强,这一点带来很大的不便,无法充分发挥JavaScript的潜能.龙其考虑到 ...
随机推荐
- python 如何解决高并发下的库存问题??
python 提供了2种方法解决该问题的问题:1,悲观锁:2,乐观锁 悲观锁:在查询商品储存的时候加锁 select_for_update() 在发生事务的commit或者是事务的rollback时 ...
- [51Nod1623] 完美消除
link $solution:$ 首先我们可以发现一个结论,对于一个数 $x$ ,它的最低修改次数为它每位与前去中是否都比此位上的数大,有则答案 $-1$ .因为若有小数则没有办法将其答案贡献变低. ...
- 错误: JMX 连接器服务器通信错误: service:jmx:rmi://***
电脑没有空间了,正想清理一下硬盘空间,这时不知道金山毒霸啥时候装上了,就想把它卸载了,卸载的过程中看到有空间清理,随手一点,清理出了10个G,然后再打开idea运行项目就报出这个错. 错误: JMX ...
- react随笔
对React children 的深入理解 https://www.jianshu.com/p/d1975493b5ea [react]利用prop-types第三方库对组件的props中的变 ...
- C#人民币金额大写转换
人民币金额大小写转换,三行代码 //完善了一下查询到的方法,支持小数,保留2位小数 public string NumGetStr(double Num) { string[] DX_SZ = { & ...
- Huawei交换机路由器远程Telnet配置
<huawei>system-view Enter system view, return user view with Ctrl+Z.[huawei]interface g0/0/0[h ...
- HTTPS原理以及流程
一.HTTP和HTTPS的区别 HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全. HTTPS协议是由SSL+HTTP协议构建的可进行加密传输.身份认证的网 ...
- minicom - 友好易用的串口通信程序
总览 SYNOPSIS minicom [-somMlwz8] [-c on|off] [-S script] [-d entry] [-a on|off] [-t term] [-p pty] [- ...
- u-boot 用哪个lds链接脚本
顶层Makefile文件中 : ifndef LDSCRIPT #LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug ifd ...
- 每日一蠢 .kettle 下的kettle.properties文件内配置的内容不能被识别
昨天装封装好的ETL 工具 窝将环境变量中的KETTLE_HOME删除了, 结果 .kettle 下的kettle.properties文件内配置的内容不能被识别 can't parse argum ...