Web worker 與JS中異步編程的對比


0.從一道題說起

var t = true; 
setTimeout(function(){ t = false; }, 1000); 
while(t){ }
alert('end');

問,以上代碼何時alert“end”呢?
測試一下:答案是:永遠都不會alert。

解析:JavaScript引擎是單線程的,事件觸發排隊等候。所有任務按照觸發時間先后排隊處理。
上例中,排隊的順序狀態是:

| var t=true ; | while(t){}; | alert(‘end’); |

在1000ms之后,setTimeout函數也加入隊列。
while(t){ }無限循環阻塞了單線程,不管排到后面的代碼執行時間有多短,后面的代碼無法執行,一直阻塞下去。

1.瀏覽器線程

瀏覽器有這么幾大線程:UI渲染線程(用於頁面的渲染),javascript引擎線程(用於處理js),GUI事件觸發線程(用於交互)。

有時會開啟的線程:http傳輸線程定時觸發線程(定時器)

它們之間的關系是什么呢?

(1)UI渲染線程 與 javascript引擎線程 互斥

由於javascript可以操縱頁面的DOM,所以如果UI渲染線程與javascript引擎線程 不互斥的話,在UI渲染線程進行頁面渲染的同時,javascript引擎線程進行DOM修改,最終會造成DOM狀態不一致的現象。所以,當javascript引擎線程運行的時候,UI渲染線程處於凍結狀態。

(2)javascript引擎線程 與 GUI事件觸發線程(用於交互) 異步

瀏覽器開啟事件觸發線程,等待用戶動作,事件觸發線程解析為響應事件,轉移到javascript引擎線程,排隊等候,等待javascript引擎的處理。

(3)javascript引擎線程 與 http傳輸線程 異步

網頁get,post等請求,xhr異步請求都通過http傳輸線程,傳送到javascript引擎排隊,等候處理。

(4)javascript引擎線程 與 定時觸發線程(定時器) 異步

setTimeout(),setInterval()由單獨的線程 定時觸發線程 觸發,傳送到javascript引擎排隊等候,等待處理。

上述的所有的異步操作有不同的瀏覽器分配線程執行,那個先執行完就先將那個加入到異步隊列中,利用事件的輪詢執行異步隊列中的回調函數

2.xhr異步是障眼法

我們來做一個試驗:

客戶端js代碼

//jquery封裝的ajax請求,請求http://localhost:3000/login頁面
 $.ajax({
        type: "post",
        url: "http://localhost:3000/login",
        dataType: "json",
        data:{ username: username, password: password },
        success: function(data){
            if(data){
               if(data.message=="i202"){
                   alert('密碼錯誤,請重新輸入');
                   window.location.href="login";
               }else if(data.message=="i200"){
                    alert('登陸成功');
                    window.location.href="index";
                }
                else{
                    alert('沒有這個用戶名');
                   window.location.href="login";
                 }
            } else{


            }
        }
    });
    //這里有一個無限循環
    while(1){}

后端Node.js代碼:

//后台對post的響應
router.post('/login', function (req, res, next) {    
    sleep(1000);
    res.send({status:"success", message:"i200"});     
});

/**
 * 模擬sleep
 * @param sleepTime
 */
function sleep(sleepTime) {
    for(var start = +new Date; +new Date - start <= sleepTime; ) { }
}

前台將永遠不會alert(“登陸成功”)。瀏覽器通過http線程收到xhr響應,但是轉到javascript線程等待執行。javascript單線程,一次只能處理一個任務。第一個任務無限循環,后面的任務全部阻塞。

xhr異步編程實際上是一種障眼法。

3.定時器時間不准

(1)時間不准1

 setTimeout(function () { while (true) { } }, 1000);
        setTimeout(function () { alert('end 2'); }, 2000);
        setTimeout(function () { alert('end 1'); }, 100);
        alert('end');

運行這段代碼。運行結果是alert(‘end’) alert(‘end 1’)。

前兩個定時器並不能如約在規定的時間點執行哦。

(2)時間不准2

setTimeout(function(){  
   /* 代碼塊... */  
   setTimeout(arguments.callee, 10);  
}, 10);  

setInterval(function(){  
   /*代碼塊... */  
 }, 10); 

兩個定時器,本想實現相同的功能:每十秒觸發一次定時器。

但是實際上,setTimeout在10ms后才加入js執行隊列,排隊等待。所以每兩次定時器觸發的時間間隔可能 > 10ms。

setInterval每10s就向js執行隊列添加一個setInterval事件等待執行。前面的setInterval事件可能被它之前的事件阻塞,導致執行晚了幾拍。那么沒兩次定時時間觸發的時間間隔可能 <10ms 。

4.web worker 才是真正多線程

來吧,試驗一下:

index.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<script src="js/Fthread.js"></script>
</body>
</html>

Fthread.js

//這里創建一個webworker就是開一個新的線程
var worker=new Worker('js/Sthread.js');//創建子線程
//這里接收新的線程傳來的data
worker.onmessage = function(event) {
    console.log(event.data);
};
//這個將會觸發向子進程的請求
worker.postMessage("begin");

//構造一個無限循環
//setTimeout(function () { while (true) { } }, 1000);該定時器不會阻塞線程的交互

Sthread.js

//這里占有一個新線程,向主線程發送消息
postMessage('hello');

//實現之前的一個實例,看是否阻塞
setTimeout(function () { console.log('end 2'); }, 2000);
setTimeout(function () { console.log('end 1'); }, 100);
worker.onmessage = function(event) {
console.log(event.data);
};
console.log(
'end');

運行結果:

end
hello  //這是兩個線程數據的傳送,可以不看end1 end2
begin end1 end2

hello沒有被定時器異步阻塞,因為人家是在子線程運行滴。這才是多線程嘛。JS中的異步雖然也有開線程進行處理相關的操作,但是其回調函數還是需要通過主線程輪詢執行,因而並不是完全的多線程。

webworker是基於瀏覽器引擎的,為了防止出現像C++之類的的多線程之間的競態、死鎖等現象,Web Worker有如下限制:

  • 同源策略限制
  • 不能訪問頁面DOM和其他資源
  • 瀏覽器實現程度不同

同時web worker線程與主線程不共享作用域(只是進行簡單消息傳遞而非數據的引用),這也會有問題當兩者在進行消息通訊時會將同一個變量賦值兩次,導致內存的占用增大,可以通過對大的數據集利用Transferable對象,進行所有權的傳遞,當數據傳遞出去后,原來的線程中將對該數據集失去訪問的權限,這樣可以避免作用域共享帶來的問題,當然,所有權的傳遞是雙向的。

  我們可以在worker內部做這些事情:

  • 可以執行網絡操作(Ajax、Web Sockets)
  • 可以使用定時器(set/clearTimeout()、set/clearInterval())
  • 訪問某些重要全局變量及功能的復本(navigator、location、JSON、applicationCache)
  • 可以使用importScrips()加載額外js腳本

Web worker有兩種形式一個是共享web worker(多對一,通過端口號進行區分),另一個是專用 web worker(實現一對一),上面的那個例子就是專用web worker的例子。

下面舉一個共享web worker 的例子:

1、創建共享worker

var worker = new SharedWorker('scripts/worker.js');

2、每個主程序利用端口號區分(多個主程序的初始化是一樣的,只是在初始化時分配的端口不同)

//demo.js
var worker = new SharedWorker('scripts/worker.js');
worker.port.addEventListener('message', function(e){
  console.log(e.data); //500000000067109000
}, false);
worker.port.start();啟動web worker,初始化端口

3、worker線程中的內容

//worker.js
function calculate(){
  var ret = 0;
  for(var i = 1; i <= 1e9; i++){
    ret += i;
  }
  return ret;
}
addEventListener('connect', function(e){
  var port = e.ports[0];//用於獲取連接分配的端口,區分主線程
  port.start();
  port.postMessage(calculate());
});

4、其他的使用如終止web worker有兩種方式,使用和專用線程是一樣的

//主線程
worker.terminate();
//工作線程
close();

轉載請注明出處;http://www.cnblogs.com/heshan1992/p/6698069.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM