深入理解Web Workers

一.Web Workers是什么

Web Workers是JavaScript运行在浏览器中的一种能力,它允许在主线程创建Worker线程,主线程执行的同时,Worker线程在后台运行,互不干扰,这并不是说JavaScript本身具有多线程的能力,是js运行在webkit浏览器中,浏览器为其启动了新的线程,从而实现多线程的功能.

二.Web Workers分类

Web Workers中主要的两种线程为专用线程Dedicated Worker和共享线程 Shared Worker,专用线程供单页面使用,即专用线程不能被多个不同的页面使用,共享线程能被多个不同的页面使用.

2.1.专用线程(Dedicated Worker)

2.1.1专用线程实例

在输入框输入一个数,计算出从1到该数字的和

(1).主线程代码

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
  </head>
  <body>
    <div class="web-worker">
      <input type="text" placeholder="请输入要计算到的数字" id="num" />
      <button onclick="calculate()">计算</button>
    </div>
  </body>
  <script>
  function calculate() {
    var dom = document.getElementById("num");
    var value = parseInt(dom.value, 10);
    worker.postMessage(value);
  }

  var worker = new Worker("worker.js");
  worker.onmessage = function(event) {
    console.log("receive data", event.data);
    worker.terminate(); //终止worker进程
  }
  worker.onerror = function(error) {
    console.log("error==", error.message, error.filename, error.lineno); 
    //error错误信息:message返回可读性良好的错误消息;filename发生错误的脚本文件名;lineno发生错误时所在脚本文件的行号
  }
  </script>
</html>

(2).Worker线程代码(Worker线程代码写在worker.js中)

onmessage = function(event) {
  console.log("worker event data", event.data);
  var value = event.data;
  var sum = 0;
  for (var i = 1; i < value; i++) {
    sum += i;
  }
  postMessage(sum);
}

(3).执行结果:
clipboard.png

2.1.2线程执行深度解析
根据以上述实例说明
(1).首先调用Worker的构造函数新建一个worker,指定一个脚本URI去执行worker线程
(2).通过postMessage发送给worker线程
(3).在worker线程中用onmessage监听消息数据,并接收封装在event参数data属性中的数据
(4).worker将处理过的数据再通过postMessage发送给主线程
(5).worker线程返回数据后,执行主线程的onmessage回调函数,调用worker.terminate()终止线程执行,当worker线程执行出错时会调用onerror回调函数.

详解:
代码执行到 var worker = new Worker("worker.js"),会在webkit内核中构造一个webCore::jsWorker对象,并根据脚本地址发起异步加载流程,此时主线程并不会阻塞,等待worker线程的执行结果,而是会接着往下执行.接着主线程执行postMessage,这时worker线程还么有创建完成,通过input输入框输入的数据将放在消息队列中等待,直到worker线程创建完毕;worker线程复制消息数据到workerRunLoop消息队列中,woker线程处理消息数据后将数据通过自身的postMessage发送,主线程执行worker.onmessage回调函数,执行完毕后关闭线程.如果主线程接着给worker线程发送数据消息,worker线程会直接将复制消息数据到WorkerRunLoop.如图所示

clipboard.png

2.1.3Transferrable objects可转让对象
主线程和子线程之间可以通过结构化克隆算法(复制副本)的方式传入传出不同类型数据,比如File,Blob,ArrayBuffer和json对象,如果用postMessag传出一个50MB的文件将会非常消耗性能,为了解决这个问题,可以将主线程中的数据直接传递给worker线程这就是Transferrable objects.

// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);

// 例子1
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
//例子2
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]);

2.1.4应用场景

(1).预先缓存并抓取数据供后期使用
(2).分析音视频数据,canvas绘图数据的运算和图形生成处理
(3).大量数据分析和计算处理
(4).拼写检查
(5).代码高亮处理或者其他一些页面文字格式化处理

2.1.5总结
学习专用线程,其实它可以和生活中的例子相结合起来,比如领导给员工分配任务,让A员工把产品的原型画出来后交给B员工去实现(领导作为主线程),A员工在接收到任务message后开始工作,工作的产出的原型就是postMessage要返回的数据,B员工在没拿到原型时还做的他之前的工作,收到A员工给的原型开始实现原型,实现完成后将产出结果给领导演示.结合这个例子可以理解专用线程的大致流程.

2.1.6兼容性

clipboard.png

2.2共享线程(Shared Worker)

2.2.1共享线程实例

两个页面分别给一个SharedWorker发送数据并接收同一个共享线程发送的数据
//A页面js代码
  var worker = new SharedWorker("./worker.js");
  var port = worker.port;
  console.log("test port", port);
  port.postMessage('test000');
  port.onmessage = function(event) {
    console.log("test receive data", event.data);
  }
//B页面js代码
  var worker = new SharedWorker('worker.js');
  var port = worker.port;
  console.log("worker1 port", port);
  port.postMessage('test111');
  port.onmessage = function(event) {
    console.log("test1 receive data", event.data);
  };
//worker.js(即SharedWorker)代码
onconnect = function(event) {
  var port = event.ports[0];
  port.onmessage = function(e) {
    port.postMessage(e.data);
  }
}

执行结果:
clipboard.png
clipboard.png

2.2.2应用场景
共享线程主要用在同一个线程被多个页面或线程使用,比如,抓取缓存数据多个页面需要使用,在浏览器兼容的情况下可以使用ShareWorker.

2.2.3总结
在测试上述例子时,用google浏览器测试,共享线程中的例子不执行,希望能得到解答和帮助

2.2.4兼容性

clipboard.png

三.专用线程和共享线程的区别和注意事项

3.1区别
(1)共享worker通信必须通过端口对象(一个确切的打开的端口)供脚本与worker通信,而专用线程在设置onmessage消息处理函数时会隐式的打开与主线程的端口连接.
3.2注意事项

(1).在写demo测试时,Google浏览器直接打开文件,会当成是跨域问题,报类似如下错误,启用本地服务器测试就可以了,用Node启用本地服务器,可以看我的另一个文章 https://segmentfault.com/a/11...
clipboard.png

(2)分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

(3)worker线程不能获取DOM,window,document,parent对象,可以获取navigator,Location,XMLHttpRequest对象,setInterval/setTimeout方法, Application Cache,可以通过importScripts()方法加载其他脚本,可以创建新的Web Worker。

四.其他类型Worker

4.1 ServiceWorkers (服务worker)

一般作为web应用程序、浏览器和网络(如果可用)之前的代理服务器。它们旨在(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动并更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API。

4.2 Chrome Workers

是一种仅适用于firefox的worker。如果您正在开发附加组件,希望在扩展程序中使用worker且有在你的worker中访问 js-ctypes 的权限,你可以使用Chrome Workers。详情请参阅ChromeWorker。

4.3 Audio Workers (音频worker)

使得在web worker上下文中直接完成脚本化音频处理成为可能。

望不对之处请指正!

主要参考文章:

1.http://www.alloyteam.com/2015...
2.https://developer.mozilla.org...
3.http://www.ruanyifeng.com/blo...
阅读 645

推荐阅读