js本身是一种单线程设计,我们无法在同一时刻并行运行多个脚本。虽然可以用setInterval,setTimeout方法来模拟多线程,但实际上这些方法都是存在于主线程使用的一个事件循环里,一旦存在一个耗时操作,就会牵制主线程的操作,导致页面卡顿。Web Worker通过引入类似线程的机制使这种问题得到了解决,通过在当前js主线程中使用worker类加载一个js文件来开辟一个新线程,起到互不阻塞执行的效果。这个js worker运行在另一个全局上下文中,不同于当前的window,所以不能用window变量来获取当前全局的范围。
* 没有共享的状态
在创建的每一个工作线程里,都无法直接访问主线程中的数据,主线程也无法直接访问工作线程中的数据,它们之间的通信都需要通过消息API来实现。
* 没有DOM访问权限
在工作线程里是无法访问DOM的,也就相应的没有window对象和document对象。
* 同源策略
只能创建和当前脚本同源的工作线程
* 浏览器兼容性
①创建一个线程:
通过new一个Worker实例来创建一个线程,构造函数参数传递一个指向js文件资源的url。
const worker = new Worker('a.js');
②与一个线程通信:
为了在页面主程序接收从专用线程传递过来的消息,我们需要使用工作线程的onmessage事件处理器。通信的数据不止是字符串,也可以是数组或者对象。
主线程:
onload =function(){
var worker =new Worker('a.js');
worker.onmessage = function (evt) {//接收子线程消息
console.log(evt.data); //hello received
};
worker.postMessage("hello")//向子线程发送消息
}
子线程:
onmessage =function(event) { //接收主线程消息
let str = event.data;
postMessage(str+" received"); //发送子线程消息
};
在主线程中,消息事件依托于创建出来的worker对象,而在工作线程中,消息事件依托于全局对象。
当工作线程完成了任务后,需要调用terminate方法来释放内存和避免僵尸线程的情况:
worker.terminate();
通常情况下是读取navigator.hardwareConcurrency的数值,它表示机器最多可以并行执行的任务数量。例如下图返回的数值是4,则可以起4个web worker来进行处理。当然也可以起更多的web worker,但是多个线程的切换以及起线程的消耗就会比较大。
WebGL里经常会对大量顶点数据和索引数据进行计算,由于js单线程的原因,很容易造成堵塞,性能低下。通过web worker可以让js在后台解决这些问题。
worker线程与主线程的数据传递使用的是结构化克隆,当数据量非常大的时候耗时严重,需用通过序列化的方式(JSON.stringfy)来传递数据。经测试,我起了四个线程来传递json对象,不序列化的时候是602ms,使用序列化的方式传递是186ms,性能明显提升。
另外,针对webgl的数据类型大多都是arraybuffer类型,web worker中有一个非常实用的参数,那就是Transferable Objects。一旦对象是 Transferable 的,那么传递过程不再使用结构化克隆,而是按引用传递,这就避免了克隆导致的额外开销。只有诸如 ArrayBuffer、ImageBitmap 这样的二进制数据类型才可以。
在Cesium当中,关于大数据量顶点的处理就是采用web worker来处理的。Cesium提供了一个TaskProcessor类来自定义一个工作线程,该类实际上就是包装了一个web worker,通过scheduleTask方法来定义异步任务。
参考资料
1.https://mp.weixin.qq.com/s?__biz=MzIzNDE0NjMzOQ==&mid=2653923832&idx=1&sn=25621f89fcd712b408634d53cd4a273a&chksm=f321d6b4c4565fa224e152ba8ded4abb599b9412abd8e8f201c30b4bfc1d0e33d08eb455694a&mpshare=1&scene=23&srcid=0516nZgoMT8Yjri8Vc1N5ug1#rd
2.https://juejin.im/entry/591946e0da2f60005df4ce5b