TA

TA

web worker簡易入門

js 是單線程的語言,由於此特性,我們在處理並發時需要用到一些技巧,如 setTimeout (),setInterval (),調用 XMLHttpRequest 等。
但這裡的並發只是非阻塞(參照 John Resig 的文章How JavaScript Timers Work),真正的多線程編程則需要 HTML5 的 web worker。

###【worker 的使用】
web worker 的使用非常簡單,線程之間通訊的 api 與 html5 postmessage 或 node.js 裡面的 socket.io 方法類似。

通訊:

  1. 發送方:postMessage (data)
  2. 接收方:onmessage (event)

終止 web worker:

  1. 子線程: self.close ()
  2. 父線程: worker.terminate ()

按照目前 w3c 規範,web worker 分為兩種:專用 worker (Dedicated Worker) 和共享 worker (Shared Worker)。

###【專用 worker】
實例化一個 web worker 對象,異步加載子線程文件 worker.js,其中的代碼將執行。

var worker = new Worker("worker.js");

給 worker 增加偵聽

worker.onmessage = function (event) {
    alert(event.data);
};

在 worker.js 裡,發送消息給父線程

postMessage('hello,imweb');

在父線程頁面就能看到發送過來的信息了。

同時,在 web worker 標準中,是支持對象參數的,也就是說我們能夠傳遞 json 數據。再看一個稍微複雜點的例子,父線程:

var worker = new Worker("worker.js");
worker.onmessage = function (event) {
	document.getElementById("result").innerHTML=event.data;
};

function start(){
	worker.postMessage({'cmd': 'start', 'msg': 'start'});
}

function pause(){
	worker.postMessage({'cmd': 'pause', 'msg': 'pause'});
}

function stop(){
	worker.postMessage({'cmd': 'stop', 'msg': 'stop'});
}

function msg(){
	worker.postMessage({'msg': 'hello imweb'});
}

worker.js:

self.onmessage = function (e) {
	var data = e.data;
  	switch (data.cmd) {
    case 'start':
    	taskStart(); //大量數據處理
      	postMessage('WORKER DO: ' + data.msg);
      	break;
    case 'pause':
    	taskPause();
      	postMessage('WORKER DO: ' + data.msg);
      	break;
    case 'stop':
      	postMessage('WORKER DO: ' + data.msg);
      	self.close(); //終止web worker
      	break;
    default:
      	postMessage('MESSAGE: ' + data.msg);
  	};
};

從上面的例子可以看到,一是利用對象參數,進程之間能夠較靈活的實現控制;二是當 woker 執行 taskStart () 處理大量數據時,只在子進程處理,不會給主頁面帶來阻塞,通常,處理大量數據會消極影響程序的響應能力,而 web worker 通過這樣的方式,能提供一個更流暢更即時的 UI。

###【共享 worker】
共享 worker 允許線程在同源中的多個頁面間進行共享,例如:同源中所有頁面或腳本可以與同一個共享線程通信。它的實例化與事件偵聽的寫法與專用 worker 略有不同,主頁面:

var worker = new SharedWorker('shared-worker.js');
worker.port.onmessage = function(e) {
    msg = 'Someone just said "' + e.data.message + '". That is message number ' + e.data.counter;
    console.log(msg);
};
worker.port.postMessage('hello shared worker!');

shared-worker.js:

var counter = 0;
var connections = [];
onconnect = function(eConn) {
	var port = eConn.ports[0]; // 此連接的特有port

//當有消息的時候通知所有的連接
port.onmessage = function(eMsg) { 
   	counter++;
   	for (var i=0; i < connections.length; i++) {
       	connections[i].postMessage({
           	message: eMsg.data,
           	counter: counter
       	});
   	}
}
port.start();
connections.push(port);

用兩個窗口打開這個頁面,第一個顯示:Someone just said "Hello shared worker!" This is message number 1,第二個也收到一樣的信息,
但是後面是message number 2

###【安全性和錯誤檢查】
出於安全性的考量,web worker 必須遵守同源策略。同時,它的全局對象是 worker 對象本身,this 和 self 引用的都是 worker 對象。
只能訪問:

  1. navigator 對象(僅限 appName, appVersion, platform, userAgent)
  2. location 對象(只讀)
  3. XMLHttpRequest
  4. setTimeout (), setInterval (), clearTimeout () 和 clearInterval () 方法

不能訪問:

  1. DOM (不是線程安全的)
  2. window 對象
  3. document 對象
  4. parent 對象

worker 內部出現錯誤時,可以用 worker.onerror 偵聽到,error 的事件有三個屬性:

  1. filename: 發生錯誤的文件名
  2. lineno: 代碼行號
  3. message: 完整的錯誤信息

如:

worker.onerror = function(e) {
	console.log(e.filename+"ERROR on line"+e.lineno+",msg:"+e.message);
}

###【web worker 的其他嘗試】
對於比較消耗時間的操作,我們可看到 web worker 能夠發揮它的作用。比如:大量數據排序,精確到像素的 canvas 計算等。而我們又知道,jsonp 加載數據時,動態創建 script 標籤,加載和執行這些過程都是阻塞式的,而 web worker 正好可以異步加載,這會是更快的方式嗎?帶著這個疑問我做了下面的試驗,分別用 jsonp 和 worker 的方式去加載文件,計算數據返回時延:

function tryJsonp(){
	var d = (new Date()).valueOf();
	var jsonp=document.createElement("script");  
    jsonp.type="text/javascript";  
    jsonp.src="worker.js?_="+d;  
    document.getElementsByTagName("head")[0].appendChild(jsonp);
    jsonp.onload = jsonp.onreadystatechange = function(){  
	   	if(!this.readyState||this.readyState=='loaded'||this.readyState=='complete'){  
	   		console.log('jsonp: '+ ((new Date()).valueOf() - d));
		}  
	}
}
function tryWorker(){
	var d = (new Date()).valueOf();
	var worker = new Worker("worker.js");
	worker.postMessage({'cmd': 'start', 'msg': 'start'});
	worker.onmessage = function (event) {
		console.log('web worker: '+ ((new Date()).valueOf() - d));
	};
}

第一次加載是一份 1k 大小的文件,每個方法重複 5 次,返回結果為:
1k 文件重複 5 次
第二次加載 1800k 大小的文件,返回結果為:
1800k 文件重複 5 次
可以看到對於較小的數據,jsonp 還是比 web worker 要快,這可能是實例化 worker 對象時帶來的影響;而數據偏大時,web worker 的加載將會更優,而且它可以異步加載。

THE END.

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。