(吐槽:浏览器js终于进入多线程时代!)
以前利用setTimeout、setInterval等方式的多线程,是伪多线程,本质上是一种在单线程中进行队列执行的方式。自从html5 web worker出现,js真正进入了多线程编程时期,现在就开始js的“真·多线程”秘籍修炼吧!
最近因为工作中的需要,使用了html5的web worker,之前一直对worker一知半解。直到看到IBM上的一篇博文(知识不是完全有效,可以当作参考),才对worker有了基本概念。
worker分类
worker分为专用线程和共享线程。专用线程只能在当前页面中访问到。而如果要多个页面访问同一个worker,就要使用共享线程,但前提是这几个页面是同域的。
worker基本用法
一、专用线程
1、在浏览器线程中(即插入页面的js代码,内联、外链的js代码都可以):
1 var worker=new Worker("testWorker.js"); 2 3 worker.onmessage=function(event){ 4 /*收到worker线程发送来的数据*/ 5 console.log(event.data); 6 }; 7 8 worker.onerror=function(event){ 9 /*收到worker线程发送来的错误信息*/ 10 console.log(event.data); 11 }; 12 13 worker.postMessage("some data");/*向worker线程发送数据*/
worker一直处于监听状态,要释放这个线程必须在浏览器线程调用worker.terminate();以释放资源。
2、在worker线程代码中:
1 this.onmessage=function(event){ 2 /*收到浏览器线程发来的信息,然后回复浏览器线程*/ 3 this.postMessage("worker received data:"+event.data+"("+new Date()+")"; 4 };
在worker线程中,如果不用接收浏览器线程发来数据或者浏览器线程不发送数据,则可以直接执行处理,最后this.postMessage(/*data*/)就行了。
在上例中,this指线程对象,所以最好的用法是在第一行代码的前面加上“var thread=this;”,然后就可以在任何地方使用thread.postMessage和thread.onmessage方法。worker线程中的代码变成:
1 var thread=this; 2 thread.onmessage=function(event){ 3 /*收到浏览器线程发来的信息,然后回复浏览器线程*/ 4 thread.postMessage("worker received data:"+event.data+"("+new Date()+")"; 5 };
二、共享线程
1、浏览器线程中:
1 var sharedWorker=new SharedWorker("sharedWorker.js"); 2 3 worker.onerror=function(e){ 4 /*worker对象错误*/ 5 console.log("create shared worker error."); 6 }; 7 worker.port.onmessage=function(event){ 8 console.log(event.data); 9 }; 10 worker.port.onerror=function(e){ 11 /*worker通信、worker线程中的错误*/ 12 console.log(e.message); 13 }; 14 15 worker.port.start();/*必须执行start以开始连接*/
在浏览器线程中,通信是通过worker的port对象进行的,每一个页面的port是不同的,具有唯一性。最关键的一点是start(),这个函数表示开始连接shared worker,连接成功时,worker线程将增加一个连接数。
在同域下的其他页面调用此shared worker,代码结构与此相同,只是处理数据的逻辑可能不同。
2、worker线程中:
1 var thread=this, 2 connectionCount=0; 3 thread.onconnect=function(e) { 4 /*有页面请求连接触发*/ 5 var port=e.ports[0];/*获取指定页面的port,用这个port和页面通信*/ 6 port.postMessage("new connection,index:"+connectionCount); 7 port.onmessage=function(event) { 8 port.postMessage("received data:"+event.data); 9 }; 10 connectionCount++; 11 };
在worker代码中,在onconnect回调函数中获取请求连接者的连接对象port,然后就可以通过这个port与请求者通信。
在这里,可以把port缓存起来,以后worker可以随时主动postMessage给浏览器特定的页面线程。这需要页面发送自己的“全局唯一特征码”供worker识别。
生命周期
专用线程的生命周期与页面的生命周期一致,可以使用worker.terminate()关闭释放线程。
共享线程的生命周期与其连接数相关,当连接数为0时,将自动关闭释放。如果需要关闭当前页面的连接,可以调用worker.port.close(),worker线程中的连接数将减少一个。关闭页面也和调用worker.port.close具有同样的影响。
尾记
1、worker也和服务器端的多线程一样,创建、销毁开销较大。所以,worker只应该用于耗时操作,例如复杂、长时间的运算。
2、worker不能访问DOM及页面相关对象如window document,这也在一定程度上限制了worker的应用,也不能在worker中创建worker。
3、worker中可以使用XMLHttpRequest,可以自己写一个包装http请求的对象到worker线程代码中,或者找个开源的http库。使用开源库需要解决如何引入的问题,还没有研究。
4、可以动态创建worker代码。提供思路,具体怎么做的忘了:) ------------ 将需要运行的代码转换为字符串,再转换为二进制的Blob对象,再使用window.URL.createObjectURL创建动态url,参数是Blob对象,然后将这个动态url传入worker的构造函数,作为第一个参数。这种一般是用在专用线程上。例子如下。
1 var doWorkByHtml5Worker = (function () { 2 //使用html5的worker进行后台耗时耗Cpu计算 3 var worker, urlContext = window.URL || window.webkitURL || window.mozURL || window.msURL; 4 var blobBuilder, blobBuilderContext = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; 5 if (blobBuilderContext) { 6 var clearBlob = function () { 7 blobBuilder = null; 8 blobBuilder = new blobBuilderContext(); 9 blobBuilder.clear = clearBlob; 10 }; 11 clearBlob(); 12 } else if (Blob) { 13 blobBuilder = { 14 builder: null, 15 append: function (str) { 16 this.builder = new Blob([str]); 17 }, 18 getBlob: function () { 19 return this.builder; 20 }, 21 clear: function () { 22 this.builder = null; 23 } 24 } 25 } 26 27 return { 28 /* 29 * workFunc为需要运行的函数,可以是字符串,也可以是原型函数,如果是函数,将在内部转换为字符串。必须为函数形式。 30 * params为需要传入函数的参数 31 * callback为执行完毕的回调函数,参数为执行workFunc返回的值 32 */ 33 Do: function (workFunc, params, callback) { 34 var workStr = workFunc.toString(); 35 if (workFunc !== workStr) { 36 workFunc = workStr; 37 } 38 blobBuilder.append('postMessage((' + workFunc.toString() + ')(' + JSON.stringify(params) + '));'); 39 worker = new Worker(urlContext.createObjectURL(blobBuilder.getBlob())); 40 worker.onmessage = function (e) { 41 this.terminate(); 42 worker = null; 43 blobBuilder.clear(); 44 callback && callback(e.data); 45 }; 46 } 47 }; 48 })();
使用:
1 /*基本演示*/ 2 doWorkByHtml5Worker.Do( 3 function(){ 4 return "data from custom function"; 5 }, 6 null, 7 function(data){ 8 /*回调*/ 9 console.log(data);/*将打印字符串"data from custom function"*/ 10 } 11 ); 12 13 /*带参数演示*/ 14 doWorkByHtml5Worker.Do( 15 function(obj){ 16 return "received object:{id:"+obj.id+",msg:\""+obj.msg+"\"}"; 17 }, 18 {id:0,msg:"go home"}, 19 function(data){ 20 /*回调*/ 21 console.log(data);/*将打印字符串"received object:{id:0,msg:"go home"}"*/ 22 } 23 ); 24 25 /*传入字符串型执行函数*/ 26 var doSomething=(function(){ 27 return "data from custom function"; 28 }).toString(); 29 /* 或者:var doSomething="function(){return \"data from custom function\";}";*/ 30 doWorkByHtml5Worker.Do( 31 doSomething, 32 null, 33 function(data){ 34 /*回调*/ 35 console.log(data);/*将打印字符串"data from custom function"*/ 36 } 37 );
参考:
1、菜鸟教程
2、IBM博文
3、W3C文档