NodeJs中的非阻塞方法


首先我們利用NodeJs先構建一個基本的服務器。

index.js

var requestHandler = require("./requestHandler");  
var server = require("./server");  
  
var route = {  
    "/hello": requestHandler.hello,  
    "/upload": requestHandler.upload  
};  
  
server.start(route);  

server.js

var http = require("http");  
var url = require("url");  
  
exports.start = function(route) {  
    var server = http.createServer(function(req, res) {  
          
        var pathName = url.parse(req.url).pathname;  
          
        var handler = route[pathName];  
          
        if (handler) {  
              
            console.log("Through path:" + pathName + ":" + new Date().getTime());  
              
            handler(res);  
              
        } else {  
            res.writeHead(404, {"Content-Type": "text/plain"});  
            res.end();  
        }  
    });  
  
    server.listen(8088);  
};  

 

requestHandler.js

exports.hello = function(res) {  
  
    res.writeHead(200, {"Content-Type": "text/plain"});  
          
    res.write("say hello.");  
          
    res.end();  
};  
  
exports.upload = function(res) {  
    res.writeHead(200, {"Content-Type": "text/plain"});  
      
    res.write("upload");  
      
    res.end();  
};

在cmd中,鍵入node index.js即可啟動。

但是,上面的代碼是阻塞的。如果在createServer的回調函數中,有花費長時間的計算。那么會阻塞node.js的事件輪詢。

NodeJS中,他的高效,關鍵在於快速的返回事件循環。

我們將requestHandler.js改造如下,在這個例子中,由於事件循環一直被sleep函數阻塞着,導致createServer的callback無法及時返回。

function sleep(milliSecond) {  
      
    var startTime = new Date().getTime();  
      
    console.log(startTime);  
      
    while(new Date().getTime() <= milliSecond + startTime) {  
          
    }  
      
    console.log(new Date().getTime());  
}  
exports.hello = function(res) {  
        sleep(20000);  
    res.writeHead(200, {"Content-Type": "text/plain"});  
          
    res.write("say hello.");  
          
    res.end();  
};  
  
exports.upload = function(res) {  
    res.writeHead(200, {"Content-Type": "text/plain"});  
      
    res.write("upload");  
      
    res.end();  
};  

 

那么先鍵入http://localhost:8088/hello,后鍵入http://localhost:8088/upload。你會發現,upload雖然不需要花費太多時間,但是卻要等到hello完成。

我們試圖找尋異步調用的方法。比如formidable中的上傳,經測試是非阻塞的。查看formidable的源碼,發現最關鍵的是下面的代碼:

IncomingForm.prototype.parse = function(req, cb) {  
  this.pause = function() {  
    try {  
      req.pause();  
    } catch (err) {  
      // the stream was destroyed  
      if (!this.ended) {  
        // before it was completed, crash & burn  
        this._error(err);  
      }  
      return false;  
    }  
    return true;  
  };  
  
  this.resume = function() {  
    try {  
      req.resume();  
    } catch (err) {  
      // the stream was destroyed  
      if (!this.ended) {  
        // before it was completed, crash & burn  
        this._error(err);  
      }  
      return false;  
    }  
  
    return true;  
  };  
  
  this.writeHeaders(req.headers);  
  
  var self = this;  
  req  
    .on('error', function(err) {  
      self._error(err);  
    })  
    .on('aborted', function() {  
      self.emit('aborted');  
    })  
    .on('data', function(buffer) {  
      self.write(buffer);  
    })  
    .on('end', function() {  
      if (self.error) {  
        return;  
      }  
  
      var err = self._parser.end();  
      if (err) {  
        self._error(err);  
      }  
    });  
  
  if (cb) {  
    var fields = {}, files = {};  
    this  
      .on('field', function(name, value) {  
        fields[name] = value;  
      })  
      .on('file', function(name, file) {  
        files[name] = file;  
      })  
      .on('error', function(err) {  
        cb(err, fields, files);  
      })  
      .on('end', function() {  
        cb(null, fields, files);  
      });  
  }  
  
  return this;  
};  

 

在parse中,將head信息解析出來這段是阻塞的。但是真正上傳文件卻是在req.on(data)中,是利用了事件驅動,是非阻塞的。也就是說,他的非阻塞模型依賴整個nodeJS事件分派架構。 

那么像sleep那樣消耗大量計算,但是又不能依賴nodeJS分派架構的時候怎么辦?

現在介紹一種,類似於html5 WebWorker的方法。

將requestHandler.js改造如下:

var childProcess = require("child_process");  
  
exports.hello = function(res) {  
      
    var n = childProcess.fork(__dirname + "/subProcess.js");  
      
    n.on('message', function() {  
          
        res.writeHead(200, {"Content-Type": "text/plain"});  
          
        res.write("say hello.");  
          
        res.end();    
    });  
      
    n.send({});  
};  
  
exports.upload = function(res) {  
    res.writeHead(200, {"Content-Type": "text/plain"});  
      
    res.write("upload");  
      
    res.end();  
};  

並加入subProcess.js

function sleep(milliSecond) {
	
	var startTime = new Date().getTime();
	
	console.log(startTime);
	
	while(new Date().getTime() <= milliSecond + startTime) {
		
	}
	
	console.log(new Date().getTime());
}

process.on('message', function() {
	sleep(20000);
	process.send({});
});

測試,當hello還在等待時,upload已經返回。

 

結語:

大概在最近,我看了博客園上的很多NodeJs文章,大家都認為NodeJS是異步的。但是是何種程度的異步,這個概念就沒有幾篇文章講對了。

其實NodeJS,他是一個雙層的架構。C++,和javascript。並且是單線程的。這點尤其重要。Node其實是C++利用v8調用js命令,為了實現調用順序維護了一個Event序列。因此,在一個js function內部,他的調用絕對會對其他的function產生阻塞。所以,網上所說的process.nextTick和setTimeout等,都不能夠產生新的線程,以保證不被阻塞。他所實現的,不過是Event序列的元素順序問題。 相對於setTimeout,process.nextTick的實現要簡單的多,直接加入Event序列的最頂層(有個啥啥事件)。而setTimeout是增加了一個c++線程,在指定的時間將callback加入Event序列

以Node的file io為例。他的readFile等函數,第二個參數是一個callback。那么node中第一件事就是記錄下callback,然后調用底層c++,調用c++開始的過程,你可以看成是異步的。因為那已經到了c++,而不是js這塊。所以,exec到callback為止是異步的,http.createServer到觸發callback為止是異步的。還有很多,比如mysql的調用方法,不知道大家有沒有看過源碼,他就是socket發送命令,相信這個過程速度非常快。然后等待回調的過程Node用c++隱藏了,他也是異步的。 

而我這篇文章想說明的是,如果再js端有花費大量時間的運算怎么辦。就用我上面所說的方法,用js打開c++的線程,這個subprocess.js,不在node的event序列內部維護。是新的線程,因此不會阻塞其他的js function


免責聲明!

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



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