Web Worker多线程
背景:
- Js语言采用的是单线程模型
- 随着计算机能力增强,多核CPU的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力
Web Worker的作用:
- 为Javascript创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行
- 主线程运行的同时,Worker线程在后台运行,两者互不干扰
- 可以降一些计算密集型或高延迟的任务,分给Worker线程负担,主线程(通常负责UI交互)就会很流畅,不会被阻塞或拖慢
- Worker线程一旦新建成功,就会始终运行,不会被主线程上的活动打断(比如用户点击按钮、提交表单)。这样有利于随时响应主线程的通信。但是,这也造成了Worker比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
注意点:
- 同源策略:分配给Worker线程运行的脚本,必须与主线程的脚本文件同源
- DOM限制:Worker线程所在的全局对象,与主线程不一样,无限读取主线程所在的网页的DOM对象,也无法使用document、window、parent这些对象。但是Worker线程可以使用navigator对象和location对象
- 通信联系:Worker线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。(postMessage发送消息,onmessage监听消息)
- 脚本限制:Worker线程不能执行alert()和confirm(),但可以使用XMLHttpRequest对象发出AJAX请求
- 文件限制:Worker线程无法读取本地文件(file://这类),它所敬爱在的脚本必须来自网络
基本用法:
//主线程
var worker = new Worker(‘./worker.js')
//向Worker发消息
worker.postMessage('hello world')
worker.postMessage({method:'echo', args:['work']})
//监听消息
worker.onmessage = function(event) {
console.log('Received message' + event.data)
doSomething()
}
function doSomething() {
//执行任务
worker.postMessage('Work done!')
//结束线程
worker.terminate()
}
//子线程
//发送消息
this.postMessage('get it!')
//self代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法
self.addEventListener('message', function (e) {
self.postMessage('You said: ' + e.data);
}, false);
//写法一
//监听消息
this.addEventListener('message', function(event) {
this.postMessage('You said' + event.data)
}, false)
// 写法二
addEventListener('message', function (e) {
postMessage('You said: ' + e.data);
}, false);
//根据主线程发来的数据,Worker 线程可以调用不同的方法,下面是一个例子。
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
self.postMessage('WORKER STARTED: ' + data.msg);
break;
case 'stop':
self.postMessage('WORKER STOPPED: ' + data.msg);
self.close(); // Terminates the worker.
break;
default:
self.postMessage('Unknown command: ' + data.msg);
};
}, false);
Worker加载脚本
//加载单个
importScripts('script1.js')
//加载多个
importScripts('script2.js', 'script3.js')
错误处理
worker.onerror(function (event) {
console.log([
'ERROR: Line', e.lineno, 'in ', e.filename, ':', e.message
].join('')
)
})
//或者
worker.addEventListener('error', function(event){
//...
})
关闭Worker
//使用完毕,为了节省系统资源,要及时关闭Worker
//主线程
worker.terminate()
//Worker线程
self.close()
数据通信
- 主线程与Worker之间的通信内容,可以是文本、对象。这种通信是深拷贝关系,Worker对通信内容修改,不影响到主线程。(浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给Worker,后者再将它还原)
- 主线程与Worker之间,也可以交换二进制数据,比如File、Blob、ArrayBuffer等
注意点:拷贝方式发送二进制数据会造成性能问题。比如主线程向Worker发送一个500MB的文件,默认情况下浏览器会生成一个源文件的拷贝。
为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。
这种转移数据的方法,叫做Transferable Objects。这使主线程可以快速将数据交给Worker,对于影像处理、声音处理、3D运算等就非常方便了,不会产生性能负担。
//直接转移数据的控制权,使用下面的写法:
worker.postMessage(arrayBuffer, [arrayBuffer])
//例如
var ab = new ArrayBuffer(1)
worker.postMessage(ab, [ab])
同页面载入Web Worker
<!DOCTYPE html>
<html>
<body>
<!--worker线程-->
<script id = "worker" type="app/worker">
addEventListener('message', function(){
postMessage('some message')
}, false)
</script>
<!--主线程-->
<script>
var blob = new Blob([document.querySelector('#worker').textContent])
var url = window.URL.createObjectURL(blob)
var worker = new Worker(url)
worker.onmessage = function(e) {
//e.tata === 'some message'
}
</script>
</body>
</html>