[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)


