前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >打造多线程 Web

打造多线程 Web

作者头像
villainhr
发布2018-07-03 15:30:07
4020
发布2018-07-03 15:30:07
举报
文章被收录于专栏:前端小吉米

由于浏览器的限制,注定了每个网页只能在一个进程程当中运行, 而且,js又只能运行在一个线程当中. 所以, 作为一名开发者来说, 对于这样的结果就只能呵呵了. 如果你想进行高复杂度的运算, 基本上就可以go die了(只要运行, 你网页基本上就崩掉了). 当然,聪明的W3C早就知道developer心里的小猫腻. 推出了web worker 这个概念. 我们接下来,来正式接触一下 web worker吧.

初入web worker

web worker 既然是一个线程. 那必定会设计到线程间的通信. 这里,ww(web worker)提供了一个最简单的方法--postMessage(msg) 进行双向通信. 来看一个简单的Demo:

代码语言:javascript
复制
// index.html 中的 main.js
var worker = new Worker('Worker.js');

worker.addEventListener('message', function(e) {
  console.log('Worker said: ', e.data);
}, false);

worker.postMessage('Hello World'); // Send data to our 

// Worker.js内容

self.addEventListener('message', function(e) {
  self.postMessage(e.data);
}, false);

当载入main.js时, 在Console里,就会出现 HelloWorld的内容. 上面的例子实际上,已经说明了worker的工作原理. 在worker中,self就是 new Wroker的实例化的内容. 不过, 这里想强调一点, 通过postMessage传递的msg并不是两个线程共享的.(要是共享的,不就GG了) 传递的Msg实际上是一个副本, 最具有代表性的,应该就算是Object.

代码语言:javascript
复制
// main.js 传递一个Object
var worker = new Worker('Worker.js');

worker.addEventListener('message', function(e) {
  console.log('Worker said: ', msg.a);
}, false);
var msg = {
    a:2
}
worker.postMessage(msg);

// worker.js 接受,并返回
self.addEventListener('message', function(e) {
  e.data.a=3;
  self.postMessage(e.data);
}, false);

// 最后返回的结果是2

在一端向另外一端传递msg时, 中间会经过serialized, 然后de-serialized 最终得到结果. 通俗一点就是:

代码语言:javascript
复制
// 传递前
JSON.stringify(msg);
// 解析数据
JSON.parse(msg);

当worker已经处理完毕,没有多大卵用之后. 就可以kill掉该线程.

关闭worker

在web中, 提供了两种方法来关闭Web Worker. 关闭指定的Worker之后, 相当于即,kill 掉该线程. 所以, 这里需要注意一下:

  • worker.terminate(): 在外部终结该worker.
  • self.close(): 在worker内部自动终结.

官方推荐是,使用self.close进行内部的自动关闭. 这样能防止, 意外关闭正在运行的worker.

worker作用域

上面,在worker.js中,我们使用self来获取worker自带的方法.

代码语言:javascript
复制
self.addEventListener('message', function(e) {
  self.postMessage(e.data);
}, false);

实际上, 在worker中, 他的全局索引就是self和this. 所以, 上面的代码可以简写为:

代码语言:javascript
复制
addEventListener('message', function(e) {
  postMessage(e.data);
}, false);

worker 可访问的 feature

worker 引用的就是js文件, 可能有些童鞋就会将worker当成一般js来使用. 但是,由于worker是独立的线程原因,他和main js threading还是有很大区别的. 他能够访问的权限有:

  • The navigator object: window.navigator 相关属性和方法
  • The location object (read-only): 只读的window.location内容.
  • XMLHttpRequest: 卧槽... 可以访问这个那就不得了了. worker就可以利用ajax来和后台进行通信了.
  • setInterval()相关时间函数

剩下的就是不能访问的了。

错误处理

web worker 中的error handler和window处理的方式,也是使用error时间进行监听.

代码语言:javascript
复制
worker.onerror = function(e){
  throw new Error(e.message + " (" + e.filename + ":" + e.lineno + ")");
};

同域限制

worker在访问时, 只能是在同一host下才行. 即, 你的worker只能处于指定目录下的path中。

代码语言:javascript
复制
// 这种情况下,就无法访问worker
new Worker('http://crossdomain.com/worker.js');

另外, 如果你使用的是本地调试 file://xxx的话, 也不能使用worker.

subworkers

在一个worker里面可以再spawn出其他的worker. 使用方法和在main.js中一样.

代码语言:javascript
复制
// 加载worker.js
var sub_worker = new Worker('subworker.js');

subworker和worker有这同样的限制, 同域, 并且他的路由是相对于parent worker. 来看一个demo吧:

代码语言:javascript
复制
// main.js
 var worker = new Worker('worker.js');
   worker.onmessage = function (event) {
     document.getElementById('result').textContent = event.data;
   };

// worker.js
  // 用来进行遍历计算
var num_workers = 10;
var items_per_worker = 1000000;

// start the workers
var result = 0;
var pending_workers = num_workers;
for (var i = 0; i < num_workers; i += 1) {
  var worker = new Worker('subworker.js');
  worker.postMessage(i * items_per_worker);
  worker.postMessage((i+1) * items_per_worker);
  worker.onmessage = storeResult;
}

// handle the results
function storeResult(event) {
  result += 1*event.data;
  pending_workers -= 1;
  if (pending_workers <= 0)
    postMessage(result); // finished!
}

// subworker.js
var start;
onmessage = getStart;
function getStart(event) {
  start = 1*event.data;
  onmessage = getEnd;
}

var end;
function getEnd(event) {
  end = 1*event.data;
  onmessage = null;
  work();
}

function work() {
  var result = 0;
  for (var i = start; i < end; i += 1) {
    // perform some complex calculation here
    result += 1;
  }
  postMessage(result);
  close();
}

另外,如果你想在当前的worker里面加载其他库文件, 就可以使用 importScripts来导入.

代码语言:javascript
复制
// 导入其他库的文件
importScripts('jquery.js','react.js','react-dom.js');

worker的用处

根据worker 独立线程这一特性. 他的使用场景也非常清晰了.反正什么大规模数据并发,I/O操作的.都可以交给他来进行. 总的来说有一下几种场景:

  • 懒加载数据
  • 文本分析
  • 流媒体数据处理
  • web database的更新
  • 大量JSON返回数据的处理

shared worker

除了大家所熟知的web worker, 或者更确切的来说--Dedicated workers. 总的来说web worker分为两种:

  • Dedicated worker (DW): 即使用 new Worker()来创建的. 该worker一般只能和creator进行通信. 即, 在创建worker的js script中才能使用.
  • Shared Wrokers (SW): 使用new SharedWorker() 进行创建. 他能在不同的js script中使用.

具体来讲SW和DW的区别就是一个只能在一个script中使用. 一个可以在不同的script中使用. 看一个简单demo:

代码语言:javascript
复制
// index.html 发起shared worker 通信

 <script>
      var worker = new SharedWorker('sharedWorker.js');
      worker.port.addEventListener("message", function(e) {             console.log(e.data);
      }, false);  
      worker.port.start();  
      // post a message to the shared web worker  
      console.log("Calling the worker from script 1");
      worker.port.postMessage("script-1");
    </script>

    <script>
      console.log("Calling the worker from script 2");
      worker.port.postMessage("script-2");
    </script>

// sharedWorker.js 内
var connections = 0; 
self.addEventListener("connect", function (e) {  
    var port = e.ports[0];  
    connections++;  
    port.addEventListener("message", function (e) {  
        port.postMessage("Welcome to " + e.data +
         " (On port #" + connections + ")");  
    }, false);  
    port.start();  
}, false);

不过, SW的兼容性比较差, 能真正在实践场景使用的地方还是少的. 所以,这里也只是当做了解. SW 和 DW 一样, 也有一些features:

  • 映入外部文件: importScripts()
  • 错误监听: error事件监听
  • 关闭通信: port.close()
  • ajax交互: 有权访问xmlHttpRequest对象
  • 能访问navigator object
  • 访问 location object
  • setTimeout等时间函数

参数传输

web worker 和 main thread 之间的通信是通过 postMessage API 来完成的。通常情况下,你可以简单的传输一些 Object,File 对象。浏览器会根据自己的 structeured cloning 算法来给你传输的对象创建一份副本。但是,如果你传输的并不是简单几个字段的对象,而是 10M+ 的内容,那么额外创建一个 copy 来说,会有点显得很没用。因为,worker 本身是额外创建进程,来处理额外的数据,加快网页速度。但是,你仅仅在传输阶段就需要额外创建一份巨大的副本,这个性能开销是非常大的。所以,Worker 借鉴了 C/C++ 的思路,创建出一个 Transferrableobjects 的概念。

TO(Transferrable objects) 本身意义是将需要传递的 big object,直接从一个上下文移交到另外一个上下文。比如,从主线程 JS 到 web worker 线程。并且,一旦移交成功,该 big object 在原有的上下文中,会变得不可访问(因为已经不在当前环境中了)。

具体使用方法很简单,还是调用 postMessage API,只是参数有些不同:

代码语言:javascript
复制
myWorker.postMessage(aMessage, transferList);
  • aMessage 就是你需要传输的内容
  • transferList[Array] 是通过一个数组来表明,那些内容是直接以 TO 的形式传过去。
代码语言:javascript
复制
worker.postMessage({data: int8View, moreData: anotherBuffer},
                   [int8View.buffer, anotherBuffer]);

inline worker

有时候在写 worker 的时候,会限制于需要额外创建一个文件不好打包。这时候,可以直接使用 inline worker 的这个特性。简单来说,就是通过 URL 对象,来讲 worker 和实际代码联系在一起。

代码语言:javascript
复制
var blob = new Blob([ "onmessage = function(e) { postMessage('msg from worker'); }"]);

// 建立联系
var blobURL = window.URL.createObjectURL(blob);

// 创建内联 worker
var worker = new Worker(blobURL);
worker.onmessage = function(e) {
  // e.data == 'msg from worker'
};
worker.postMessage();

通过 Blob,将文本转化为二进制,利用 URL 对象生成一个本地链接,这样就可以通过 blob:http://localhost:8080/8f1e22e3-7823-46ae-91d4-3e72203dc182 的形式来模拟一个 web worker 请求。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端小吉米 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初入web worker
    • 关闭worker
      • worker作用域
        • worker 可访问的 feature
          • 错误处理
            • 同域限制
              • subworkers
                • worker的用处
                  • shared worker
                    • 参数传输
                      • inline worker
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档