聊聊webWorker

先看几个例子

本例子是经过经过红点展现地球上的地震带,数据来自于地质探测局
经过console.log看到数据运算所耗的时间
不使用 webworker No web workers - all on main thread
使用一条 webworker One web worker
使用两条 Two web workers
使用八条 Eight web workers
使用20条 20 web workersjavascript

结论:是? // 带着思考看下去html

背景

JavaScript引擎是单线程运行的,JavaScript中耗时的I/O操做都被处理为异步操做,它们包括键盘、鼠标I/O输入输出事件、窗口大小的resize事件、定时器(setTimeout、setInterval)事件、Ajax请求网络I/O回调等。当这些异步任务发生的时候,它们将会被放入浏览器的事件任务队列中去,等到JavaScript运行时执行线程空闲时候才会按照队列先进先出的原则被一一执行,但终究仍是单线程。
clipboard.pnghtml5

虽然JS运行在浏览器中,是单线程的,每一个window一个JS线程,但浏览器不是单线程的,例如Webkit或是Gecko引擎,均可能有以下线程:java

javascript引擎线程
界面渲染线程
浏览器事件触发线程
Http请求线程

不少人以为异步(promise async/await),都是经过相似event loop在日常的工做中已经足够,可是若是作复杂运算,这些异步伪线程的不足就逐渐体现出来,好比settimeout拿到的值并不正确,再者假如页面有复杂运算的时候页面很容易触发假死状态,
为了有多线程功能,webworker问世了。不过,这并不意味着 JavaScript 语言自己就支持了多线程,对于 JavaScript 语言自己它还是运行在单线程上的, Web Worker 只是浏览器(宿主环境)提供的一个能力/APInode

简介

Web Worker 是HTML5标准的一部分,这一规范定义了一套 API,它容许一段JavaScript程序运行在主线程以外的另一个线程中。工做线程容许开发人员编写可以长时间运行而不被用户所中断的后台程序, 去执行事务或者逻辑,并同时保证页面对用户的及时响应,能够将一些大量计算的代码交给web worker运行而不冻结用户界面,后面会有案例介绍git

类型

Web workers可分为两种类型:专用线程dedicated web worker,以及共享线程shared web workerDedicated web worker随当前页面的关闭而结束;这意味着Dedicated web worker只能被建立它的页面访问。与之相对应的Shared web worker能够被多个页面访问。在Javascript代码中,“Work”类型表明Dedicated web worker,而“SharedWorker”类型表明Shared web worker
Shared Worker则能够被多个页面所共享(同域状况下)github

如何建立

Web Worker的建立是在主线程当中经过传入文件的url来实现的。以下所示:web

let webworker = new Worker('myworker.js');

返回的是webworker实例对象,该对象是主线程和其余线程的通信桥梁
主线程和其余线程能够经过ajax

onmessage: 监听事件
postmessage: 传送事件

相关的API进行通信
案例代码以下json

//主线程 main.js
var worker = new Worker("worker.js");
worker.onmessage = function(event){
    // 主线程收到子线程的消息
};
// 主线程向子线程发送消息
worker.postMessage({
    type: "start",
    value: 12345
});

//web worker.js
onmessage = function(event){
   // 收到
};
postMessage({
    type: "debug",
    message: "Starting processing..."
});

相关demo

如何终止

若是在某个时机不想要 Worker 继续运行了,那么咱们须要终止掉这个线程,能够调用 在主线程workerterminate 方法 或者在相应的线程中调用close

// 方式一 main.js 在主线程中止方式 
var worker = new Worker('./worker.js');
...
worker.terminate();

// 方式2、worker.js
self.close()

错误机制

提供了onerror API

worker.addEventListener('error', function (e) {
  console.log('MAIN: ', 'ERROR', e);
  console.log('filename:' + e.filename + '-message:' + e.message + '-lineno:' + e.lineno);
});

// event.filename: 致使错误的 Worker 脚本的名称;
// event.message: 错误的信息;
// event.lineno: 出现错误的行号;

sharedWorker

对于 Web Worker ,一个 tab 页面只能对应一个 Worker 线程,是相互独立的;
SharedWorker 提供了能力可以让不一样标签中页面共享的同一个 Worker 脚本线程;
固然,有个很重要的限制就是它们须要知足同源策略,也就是须要在同域下;
在页面(能够多个)中实例化 Worker 线程:

// main.js

var myWorker = new SharedWorker("worker.js");

myWorker.port.start();

myWorker.port.postMessage("hello, I'm main");

myWorker.port.onmessage = function(e) {
  console.log('Message received from worker');
}
// worker.js
onconnect = function(e) {
  var port = e.ports[0];

  port.addEventListener('message', function(e) {
    var workerResult = 'Result: ' + (e.data[0]);
    port.postMessage(workerResult);
  });
  port.start();
}

在线demo

父子线程

线程中再建立线程

环境与做用域

Worker 线程的运行环境中没有 window 全局对象,也没法访问 DOM 对象,因此通常来讲他只能来执行纯 JavaScript 的计算操做。可是,他仍是能够获取到部分浏览器提供的 API 的:

setTimeout(), clearTimeout(), setInterval(), clearInterval():有了设计个函数,就能够在 Worker : 线程中能够再建立worker;
XMLHttpRequest : 对象:意味着咱们能够在 Worker 线程中执行 ajax 请求;
navigator 对象:能够获取到 ppName,appVersion,platform,userAgent 等信息;
location 对象(只读):能够获取到有关当前 URL 的信息;
Application Cache
indexedDB
WebSocket、
Promise、

库或外部脚本引入和访问

在线程中,提供了importScripts方法
若是线程中使用了importScripts 通常按照如下步骤解析

一、解析 importScripts方法的每个参数。
二、若是有任何失败或者错误,抛出 SYNTAX_ERR 异常。
三、尝试从用户提供的 URL 资源位置处获取脚本资源。
四、对于 importScripts 方法的每个参数,按照用户的提供顺序,获取脚本资源后继续进行其它操做。
// worker.js
importScripts('math_utilities.js'); 
onmessage = function (event) 
 { 
     var first=event.data.first; 
     var second=event.data.second; 
     calculate(first,second); // calculate 是math_utilities.js中的方法 
 };

也能够一次性引入多个

//能够多起一次传入
importScripts('script1.js', 'script2.js');

XMLHttpRequest

onmessage = function(evt){
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "serviceUrl"); //serviceUrl为后端j返回son数据的接口
    xhr.onload = function(){
    postMessage(xhr.responseText);
    };
    xhr.send();
}
jsonp
// 设置jsonp
function MakeServerRequest() 
{
    importScripts("http://SomeServer.com?jsonp=HandleRequest");
} 

// jsonp回调
function HandleRequest(objJSON) 
{
    postMessage("Data returned from the server...FirstName: " 
                  + objJSON.FirstName + " LastName: " + objJSON.LastName);
} 

// Trigger the server request for the JSONP data 
MakeServerRequest();

通信原理

从一个线程到另外一个线程的通信其实是一个值拷贝的过程,其实是先将数据JSON.stringify以后再JSON.parse。主线程与子线程之间也能够交换二进制数据,好比File、Blob、ArrayBuffer等对象,也能够在线程之间发送。可是,用拷贝方式发送二进制数据,会形成性能问题。好比,主线程向子线程发送一个50MB文件,默认状况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript容许主线程把二进制数据直接转移给子线程,转移后主线程没法再使用这些数据,这是为了防止出现多个线程同时修改数据的问题,这种转移数据的方法,叫作Transferable Objects。
不过如今不少浏览器支持transferable objects(可转让对象) ,这个技术是零拷贝转移,能大大提高性能,
能够指定传送的数据全都是零拷贝

var abBuffer = new ArrayBuffer(32);
aDedicatedWorker.postMessage(abBuffer, [abBuffer]);

也能够 指定某个是 使用 零拷贝

var objData = {
   "employeeId": 103,
   "name": "Sam Smith",
   "dateHired": new Date(2006, 11, 15),
   "abBuffer": new ArrayBuffer(32)
};
aDedicatedWorker.postMessage(objData, [objData.abBuffer]);

工做线程生命周期

工做线程之间的通讯必须依赖于浏览器的上下文环境,而且经过它们的 MessagePort 对象实例传递消息。每一个工做线程的全局做用域都拥有这些线程的端口列表,这些列表包括了全部线程使用到的 MessagePort 对象。在专用线程的状况下,这个列表还会包含隐式的 MessagePort 对象。
每一个工做线程的全局做用域对象 WorkerGlobalScope 还会有一个工做线程的线程列表,在初始化时这个列表为空。当工做线程被建立的时候或者拥有父工做线程的时候,它们就会被填充进来。
最后,每一个工做线程的全局做用域对象 WorkerGlobalScope 还拥有这个线程的文档模型,在初始化时这个列表为空。当工做线程被建立的时候,文档对象就会被填充进来。不管什么时候当一个文档对象被丢弃的时候,它就要从这个文档对象列举里面删除出来。

性能测试

初始化测试
// 部分机器webwoker初始化时间
Macbook Pro: 2 workers, 0.4 milliseconds on average
Macbook Pro: 4 workers, 0.6 milliseconds on average
Nexus 5: 2 workers, 6 milliseconds on average
Nexus 5: 4 workers, 15 milliseconds on average (border-line UI jank)
传输速度测试

一、普通json/object
clipboard.png

二、tranferable objects

clipboard.png
可见 transferable objects传输速度要高不少

部分典型的应用场景以下

1) 使用专用线程进行数学运算
Web Worker最简单的应用就是用来作后台计算,而这种计算并不会中断前台用户的操做
2) 图像处理
经过使用从<canvas>或者<video>元素中获取的数据,能够把图像分割成几个不一样的区域而且把它们推送给并行的不一样Workers来作计算
3) 大量数据的检索
当须要在调用 ajax后处理大量的数据,若是处理这些数据所需的时间长短很是重要,能够在Web Worker中来作这些,避免冻结UI线程
4) 背景数据分析
因为在使用Web Worker的时候,咱们有更多潜在的CPU可用时间,咱们如今能够考虑一下JavaScript中的新应用场景。例如,咱们能够想像在不影响UI体验的状况下实时处理用户输入。利用这样一种可能,咱们能够想像一个像Word(Office Web Apps 套装)同样的应用:当用户打字时后台在词典中进行查找,帮助用户自动纠错等等。

限制

一、不能访问DOM和BOM对象的,Location和navigator的只读访问,而且navigator封装成了WorkerNavigator对象,更改部分属性。没法读取本地文件系统
二、子线程和父级线程的通信是经过值拷贝,子线程对通讯内容的修改,不会影响到主线程。在通信过程当中值过大也会影响到性能(解决这个问题能够用transferable objects
三、并不是真的多线程,多线程是由于浏览器的功能
四、兼容性
5 由于线程是经过importScripts引入外部的js,而且直接执行,实际上是不安全的,很容易被外部注入一些恶意代码
六、条数限制,大多浏览器能建立webworker线程的条数是有限制的,虽然能够手动去拓展,可是若是不设置的话,基本上都在20条之内,每条线程大概5M左右,须要手动关掉一些不用的线程才可以建立新的线程(相关解决方案
七、js存在真的线程的东西,好比SharedArrayBuffer

clipboard.png

js的多线程库

一、tagg2

参考文献:[1] https://www.html5rocks.com/zh...
[2] http://www.alloyteam.com/2015...
[3] https://typedarray.org/concur...
[4] http://www.andygup.net/advanc...
[5] https://developer.mozilla.org...
[6] http://coolaj86.github.io/htm...
[7] http://www.xyhtml5.com/webwor...

相关文章
相关标签/搜索