[Firefox 3.5 preview] web worker thread
Fri, 26 Jun 2009 21:13:39 +0800Firefox 3.5 RC剛剛出來,在他的 Release Note 裡面提到了一些新的功能,包含native JSON support以及web worker thread這兩個新的javascript功能。(另外,透過安裝addon,還可以支援canvas 3d)
如果熟悉Javascript,應該就知道他是single thread的。現在透過web worker,可以讓javascript擁有一些真正多工的能力(根據說明,他會使用到系統層級的thread)。
雖然web worker使用上有一些限制,例如每次要新增一個worker,都可能會有網路的loading,不過適當地使用還是對效率有所幫助。
使用上有幾個地方要先注意:
- worker執行的環境是一個完全獨立的context,這個context裡面的Global物件幾乎沒什麼東西,只包含了onmessage事件屬性用來指定事件處理函數,以及postMessage用來回傳資料並觸發worker物件的onmessage事件。
- worker物件有同樣有onmessage屬性,指定給他的事件處理函數會在worker載入的Javascript執行中呼叫postMessage時被觸發,而postMessage方法則可以觸發載入的Javascript中的onmessage。
- 根據Mozilla的文件說明,透過postMessage傳遞的資料,會先轉成JSON再轉回成物件,並不是直接把一個物件的參考傳入,所以針對接收到的物件所作的修改,對傳入的物件完全不會有作用。
- 簡單地說,在worker所載入執行的Javascript裡面,完全無法操控DOM,能做的只有接收傳進來的資料並傳回處理過的結果。
接下來就試用一下吧。先不做困難的東西,只簡單地比較一些使用worker的方法及效率。程式碼如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <title>test486.html</title> <body> <div id="panel"></div> <script type="text/javascript"> var workers = []; var str = ""; var count = 0; var start = new Date().getTime(); var total = 0; for(var i=0; i<4; i++) { workers[i] = new Worker('test486.js'); workers[i].onmessage = function(event) { str += event.data +"<br>"; count++; if(count == 100) { total = new Date().getTime()-start; document.getElementById('panel').innerHTML = str; alert(total/1000); for (var x=0; x<4; x++) { workers[x].terminate(); workers[x] = null; } workers = null; count = 0; str = ""; test2(); } } } for (var i=0; i<25; i++) { for(var j=0; j<4; j++) { workers[j].postMessage([j,new Date().getTime()]); } } function test2() { start = new Date().getTime(); workers = []; for (var i=0; i<100; i++) { workers[i] = new Worker('test486.js'); workers[i].onmessage = function(event) { str += event.data +"<br>"; count++; if(count == 100) { total = new Date().getTime()-start; document.getElementById('panel').innerHTML = str; alert(total/1000); for (var j=0; j<100; j++) { workers[j].terminate(); workers[j] = null; } workers = null; } } workers[i].postMessage([i, new Date().getTime()]); } } </script> </body> </html>
載入的test486.js內容如下:
var result = []; onmessage = function(event) { result.push(event.data[1]); postMessage(event.data[0]+":"+result); };
第一個部份是用四個web worker模仿thread pool,用這四個worker來進行一百個task。
另一個是產生一百個web worker來進行一百個task:
速度慢了不少,可以看出每一個worker都有一些overhead,所以使用時要斟酌。
另外,在test486.js中的全域變數並不會因為onmessage事件執行完畢就消失,worker只是在一個thread中等待事件發生然後執行處理函數,每次執行都會影響相關的變數。
另外,不論是在worker或是網頁中,事件處理函數的執行都是用一個佇列在管理,這樣就不會有racing發生,所以不用特別lock住相關的變數,系統一定會根據佇列中執行的順序去更動變數,但是事件可能不會依照預期的順序發生就是了。
另外試作複雜一點的例子,用worker來做partial reduce:
<html> <title>test487.html</title> <body> <div id="panel"></div> <script> var str = ""; var test = []; for(var i=0; i<10000; i++) { if(i<5000) { test[i] = 0.01; } else { test[i] = 0.02; } } var workers = []; var start = new Date().getTime(); var status = []; var poolsize = 6; for (var i=0; i<poolsize; i++) { workers[i] = new Worker('test487.js'); workers[i].onmessage = function(event) { test.unshift(event.data); str += event.data + "<br>"; if (test.length<2) { status.push(1); if (status.length == poolsize) { alert((new Date().getTime()-start)/1000); alert(test); document.getElementById("panel").innerHTML = str; for (var x=0; x<poolsize; x++) { workers[x].terminate(); } } } else { this.postMessage([test.pop(), test.pop()]); } } workers[i].postMessage([test.pop(), test.pop()]); } </script> </body> </html>
載入的test487.js內容如下:
onmessage = function(event) { postMessage((event.data[0] + event.data[1])); }
web worker的global object裡面同樣有XMLHttpRequest物件可以使用,用他在背景取得ajax內容完全不會影響頁面程式的執行,這也許是最有用的功能吧?Mozilla網站上有一些簡要的說明,可以參考一下:Using web workers,裡面還有一些連結可以當作進一步的參考。(包括HTML5裡面的worker規格文件,web worker只是worker的一種,不過目前Firefox 3.5也只支援web worker)