Javascript运行在单线程环境中,虽然单线程避免了多线程的难点,但缺点也很明显,如果某个任务耗时很久,就无法处理其他任务,会让用户感觉浏览器卡住了。所以浏览器中除了JS引擎线程,还有其他线程,比如 GUI 渲染线程、http 请求线程、事件触发线程等,而通过事件循环机制对setTimeout/setInterval、ajax和dom事件的异步处理,就需要多个线程的参与。
HTML5引进了 Web Worker,可让JS在后台运行,执行耗时长的任务,而不影响主页面代码的执行。
1.定义:Web Worker 是HTML5标准的一部分,允许一段JavaScript程序运行在主线程之外的另外一个线程中。Web Worker 规范中定义了两类工作线程,分别是专用线程Dedicated Worker和共享线程 Shared Worker,其中,Dedicated Worker只能为一个页面所使用,而Shared Worker可以被多个页面所共享,本文以前者为例。
2.示例:
主页面代码 main.js
// 一个较大的数组,其元素由随机数构成,传到worker中去异步处理
let arr=[]
for(let i=0;i<1000000;i++){
let item=Math.round(Math.random()*100);
arr.push(item);
}
// 这行代码会导致浏览器加载(下载)worker.js文件,但不会立即执行
let worker=new Worker('./worker.js')
// 收到 worker 的返回结果
worker.onmessage=function (event) {
let data=event.data;
console.log(data);
worker.terminate();//终止worker的执行
}
// worker内部js执行出错
worker.onerror=function (event) {
console.log(`Error ${event.filename } in line ${event.lineno}: ${event.message}`);
}
// 只有给worker传递消息后,它才会执行相应文件中的代码
// 而且数据会以异步方式被传递给worker
worker.postMessage(arr);
在主页面main.js代码中,实例化 Worker 对象并传入要执行的 JS 文件名,这一对象可视为主线程中对新创建的工作线程的引用。然后调用worker.postMessage()方法,给新创建的工作线程传递消息,消息内容可以是任何可被序列化的值(一般而言,能够被序列化为 JSON 结构的任何值都可作为参数被传递给 postMessage。而且传入的值是复制到 worker中,而不是直接传过去,即在 worker 中对传入值的处理,不会影响主页面中的原始值)。
worker 是通过message
和error
与主页面通信的,在主页面中分别定义 onmessage
事件和onerror
事件的回调处理函数,当woker线程返回数据时,onmessage回调函数执行,数据封装在event参数的data属性中。任何时候调用 worker 的 terminate()方法会立即终止worker中代码执行(后续过程不再发生,包括 message 和 error 事件也不会被触发);当worker线程执行出错时(只要 worker 内部的 JS 在执行过程中遇到错误),onerror回调函数执行,event参数中封装了错误对象的文件名、出错行号和具体错误信息。
Dedicated Worker所执行的代码worker.js,例如是计算量较大的 js 处理。
self.onmessage=function (event) {
let data=event.data.sort((v1,v2)=>v1-v2);
self.postMessage(data);
self.close();//停止工作
}
在worker.js代码中,定义了onmessage事件处理函数,由主线程传入的数据,封装在event.data中,数据处理完成后,通过postMessage方法完成与主线程通信。在工作线程代码中,onmessage事件和postMessage方法在其全局作用域可以访问。在 Worker 内部,可以调用 close() 方法停止工作,效果和在主页面中调用 terminate() 方法一样。
3. Worker全局作用域
Web Worker 所执行的 JS 代码完全在另一个作用域中,与主页面中的代码不共享作用域(在上文例子中,main.js 和 worker.js 中的代码作用域互相独立)。在 Web Worker中,同样有一个全局对象以及其他对象和方法,但是 Web Worker 中的代码不能访问 dom,也无法影响页面外观。
Web Worker中的全局对象就是 worker 对象本身(注意,与主页面 mian.js 中的 worker 不是同一个对象),即在这个特殊的全局作用域里,this 和 self 指向的都是 worker 对象。Web Worker 本身就是一个最小化的运行环境。
在Web Worker中,可以获得下列对象、方法
(1)navigator对象
(2)location对象,只读
(3)XMLHttpRequest对象
(4)setTimeout/setInterval方法
(5)Application Cache
(6)通过importScripts()方法加载其他脚本
(7)创建新的Web Worker
但不能获得下列对象
(1)DOM对象
(2)window对象
(3)document对象
(4)parent对象
上述的规范,限制了在worker线程中获得主线程页面相关对象的能力,所以在worker线程中,不能访问dom元素,避免出现混乱的 dom 操作。
4. Worker线程执行流程
webKit加载并执行worker线程的流程如下图所示
解释:
(1)worker线程的创建的是异步的
代码执行到"var worker = new Worker(task.js')“时,在内核中构造WebCore::JSWorker对象(JSBbindings层)以及对应的WebCore::Worker对象(WebCore模块),根据初始化的url地址"task.js"发起异步加载的流程;主线程代码不会阻塞在这里等待worker线程去加载、执行指定的脚本文件,而是会立即向下继续执行后面代码。
(2)postMessage消息交互由内核调度
main.js中,在创建woker线程后,立即调用了postMessage方法传递了数据,在worker线程还没创建完成时,main.js中发出的消息,会先存储在一个临时消息队列中,当异步创建worker线程完成,临时消息队列中的消息数据复制到woker对应的WorkerRunLoop的消息队列中,worker线程开始处理消息。在经过一轮消息来回后,继续通信时, 这个时候因为worker线程已经创建,所以消息会直接添加到WorkerRunLoop的消息队列中;
5. Worker线程数据通讯方式
主线程与子线程数据通信方式有多种,通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是地址,子线程对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给子线程,后者再将它还原。
主线程与子线程之间也可以交换二进制数据,比如File、Blob、ArrayBuffer等对象,也可以在线程之间发送。但是,用拷贝方式发送二进制数据,会造成性能问题。比如,主线程向子线程发送一个50MB文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,转移后主线程无法再使用这些数据,这是为了防止出现多个线程同时修改数据的问题,这种转移数据的方法,叫做Transferable Objects。
// Create a 32MB "file" and fill it.
let uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (let i = 0,len=uInt8Array .length ; i <len ; ++i) {
uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
6. Web Worker作用
Web Worker并没有为Javascript带来多线程编程能力,JS 代码的执行仍然是单线程的。Web Worker带来的是后台计算能力。
Web Worker自身是由webkit多线程实现,但它并没有为Javasctipt语言带来多线程编程特性,我们现在仍然不能在Javascript代码中创建并管理一个线程,或者主动控制线程间的同步与锁等特性。
在我看来,Web Worker可以说是worker编程模型在浏览器端Javascript语言中的应用。浏览器的运行时,同其他GUI程序类似,核心逻辑像是下面这个无限循环:
while(true){
1 处理数据和更新对象状态
2 渲染可视化UI
}
在Web Worker之前,Javascript执行引擎只能在一个单线程环境中完成这两项任务。Web Worker的引入,是借鉴了worker编程模型,给单线程的Javascript带来了后台计算的能力。
既然Web Worker为浏览器端Javascript带来了后台计算能力,我们便可利用这一能力,将无限循环中第一项“处理数据和更新对象状态 ”的耗时部分交由Web Worker执行,提升页面性能。
部分典型的应用场景如下:
(1)使用专用线程进行数学运算
Web Worker最简单的应用就是用来做后台计算,而这种计算并不会中断主页面的代码执行。
(2)图像处理
通过使用从<canvas>或者<video>元素中获取的数据,可以把图像分割成几个不同的区域并且把它们推送给并行的不同Workers来做计算(如将彩色图像转换成灰阶图像)。
(3)大量数据的检索
当需要在调用 ajax后处理大量的数据,如果处理这些数据所需的时间长短非常重要,可以在Web Worker中来做这些,避免冻结UI线程。
reference
1.The Basics of Web Workers
http://www.html5rocks.com/en/tutorials/workers/basics/
- 深入 HTML5 Web Worker 应用实践:多线程编程
http://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/index.html - JavaScript 工作线程实现方式
http://www.ibm.com/developerworks/cn/web/1105_chengfu_jsworker/index.html
4.HTML5 与 ”性工能“障碍
http://fins.iteye.com/blog/1747321
5.Web Worker在WebKit中的实现机制
http://blog.csdn.net/codigger/article/details/40581343