NodeJs中的非阻塞方法介紹
更新時(shí)間:2012年06月05日 22:41:38 作者:
NodeJs中的非阻塞方法介紹,需要的朋友可以參考下
首先我們利用NodeJs先構(gòu)建一個(gè)基本的服務(wù)器。
index.js
var requestHandler = require("./requestHandler");
var server = require("./server");
var route = {
"/hello": requestHandler.hello,
"/upload": requestHandler.upload
};
server.start(route);
server.js
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即可啟動(dòng)。
但是,上面的代碼是阻塞的。如果在createServer的回調(diào)函數(shù)中,有花費(fèi)長(zhǎng)時(shí)間的計(jì)算。那么會(huì)阻塞node.js的事件輪詢。
NodeJS中,他的高效,關(guān)鍵在于快速的返回事件循環(huán)。
我們將requestHandler.js改造如下,在這個(gè)例子中,由于事件循環(huán)一直被sleep函數(shù)阻塞著,導(dǎo)致createServer的callback無法及時(shí)返回。
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。你會(huì)發(fā)現(xiàn),upload雖然不需要花費(fèi)太多時(shí)間,但是卻要等到hello完成。
我們?cè)噲D找尋異步調(diào)用的方法。比如formidable中的上傳,經(jīng)測(cè)試是非阻塞的。查看formidable的源碼,發(fā)現(xiàn)最關(guān)鍵的是下面的代碼:
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)中,是利用了事件驅(qū)動(dòng),是非阻塞的。也就是說,他的非阻塞模型依賴整個(gè)nodeJS事件分派架構(gòu)。
那么像sleep那樣消耗大量計(jì)算,但是又不能依賴nodeJS分派架構(gòu)的時(shí)候怎么辦?
現(xiàn)在介紹一種,類似于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({});
});
測(cè)試,當(dāng)hello還在等待時(shí),upload已經(jīng)返回。
結(jié)語:
大概在最近,我看了博客園上的很多NodeJs文章,大家都認(rèn)為NodeJS是異步的。但是是何種程度的異步,這個(gè)概念就沒有幾篇文章講對(duì)了。
其實(shí)NodeJS,他是一個(gè)雙層的架構(gòu)。C++,和javascript。并且是單線程的。這點(diǎn)尤其重要。Node其實(shí)是C++利用v8調(diào)用js命令,為了實(shí)現(xiàn)調(diào)用順序維護(hù)了一個(gè)Event序列。因此,在一個(gè)js function內(nèi)部,他的調(diào)用絕對(duì)會(huì)對(duì)其他的function產(chǎn)生阻塞。所以,網(wǎng)上所說的process.nextTick和setTimeout等,都不能夠產(chǎn)生新的線程,以保證不被阻塞。他所實(shí)現(xiàn)的,不過是Event序列的元素順序問題。 相對(duì)于setTimeout,process.nextTick的實(shí)現(xiàn)要簡(jiǎn)單的多,直接加入Event序列的最頂層(有個(gè)啥啥事件)。而setTimeout是增加了一個(gè)c++線程,在指定的時(shí)間將callback加入Event序列
以Node的file io為例。他的readFile等函數(shù),第二個(gè)參數(shù)是一個(gè)callback。那么node中第一件事就是記錄下callback,然后調(diào)用底層c++,調(diào)用c++開始的過程,你可以看成是異步的。因?yàn)槟且呀?jīng)到了c++,而不是js這塊。所以,exec到callback為止是異步的,http.createServer到觸發(fā)callback為止是異步的。還有很多,比如mysql的調(diào)用方法,不知道大家有沒有看過源碼,他就是socket發(fā)送命令,相信這個(gè)過程速度非常快。然后等待回調(diào)的過程N(yùn)ode用c++隱藏了,他也是異步的。
而我這篇文章想說明的是,如果再js端有花費(fèi)大量時(shí)間的運(yùn)算怎么辦。就用我上面所說的方法,用js打開c++的線程,這個(gè)subprocess.js,不在node的event序列內(nèi)部維護(hù)。是新的線程,因此不會(huì)阻塞其他的js function
index.js
復(fù)制代碼 代碼如下:
var requestHandler = require("./requestHandler");
var server = require("./server");
var route = {
"/hello": requestHandler.hello,
"/upload": requestHandler.upload
};
server.start(route);
server.js
復(fù)制代碼 代碼如下:
server.js
復(fù)制代碼 代碼如下:
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
復(fù)制代碼 代碼如下:
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即可啟動(dòng)。
但是,上面的代碼是阻塞的。如果在createServer的回調(diào)函數(shù)中,有花費(fèi)長(zhǎng)時(shí)間的計(jì)算。那么會(huì)阻塞node.js的事件輪詢。
NodeJS中,他的高效,關(guān)鍵在于快速的返回事件循環(huán)。
我們將requestHandler.js改造如下,在這個(gè)例子中,由于事件循環(huán)一直被sleep函數(shù)阻塞著,導(dǎo)致createServer的callback無法及時(shí)返回。
復(fù)制代碼 代碼如下:
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。你會(huì)發(fā)現(xiàn),upload雖然不需要花費(fèi)太多時(shí)間,但是卻要等到hello完成。
我們?cè)噲D找尋異步調(diào)用的方法。比如formidable中的上傳,經(jīng)測(cè)試是非阻塞的。查看formidable的源碼,發(fā)現(xiàn)最關(guān)鍵的是下面的代碼:
復(fù)制代碼 代碼如下:
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)中,是利用了事件驅(qū)動(dòng),是非阻塞的。也就是說,他的非阻塞模型依賴整個(gè)nodeJS事件分派架構(gòu)。
那么像sleep那樣消耗大量計(jì)算,但是又不能依賴nodeJS分派架構(gòu)的時(shí)候怎么辦?
現(xiàn)在介紹一種,類似于html5 WebWorker的方法。
將requestHandler.js改造如下:
復(fù)制代碼 代碼如下:
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
復(fù)制代碼 代碼如下:
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({});
});
測(cè)試,當(dāng)hello還在等待時(shí),upload已經(jīng)返回。
結(jié)語:
大概在最近,我看了博客園上的很多NodeJs文章,大家都認(rèn)為NodeJS是異步的。但是是何種程度的異步,這個(gè)概念就沒有幾篇文章講對(duì)了。
其實(shí)NodeJS,他是一個(gè)雙層的架構(gòu)。C++,和javascript。并且是單線程的。這點(diǎn)尤其重要。Node其實(shí)是C++利用v8調(diào)用js命令,為了實(shí)現(xiàn)調(diào)用順序維護(hù)了一個(gè)Event序列。因此,在一個(gè)js function內(nèi)部,他的調(diào)用絕對(duì)會(huì)對(duì)其他的function產(chǎn)生阻塞。所以,網(wǎng)上所說的process.nextTick和setTimeout等,都不能夠產(chǎn)生新的線程,以保證不被阻塞。他所實(shí)現(xiàn)的,不過是Event序列的元素順序問題。 相對(duì)于setTimeout,process.nextTick的實(shí)現(xiàn)要簡(jiǎn)單的多,直接加入Event序列的最頂層(有個(gè)啥啥事件)。而setTimeout是增加了一個(gè)c++線程,在指定的時(shí)間將callback加入Event序列
以Node的file io為例。他的readFile等函數(shù),第二個(gè)參數(shù)是一個(gè)callback。那么node中第一件事就是記錄下callback,然后調(diào)用底層c++,調(diào)用c++開始的過程,你可以看成是異步的。因?yàn)槟且呀?jīng)到了c++,而不是js這塊。所以,exec到callback為止是異步的,http.createServer到觸發(fā)callback為止是異步的。還有很多,比如mysql的調(diào)用方法,不知道大家有沒有看過源碼,他就是socket發(fā)送命令,相信這個(gè)過程速度非常快。然后等待回調(diào)的過程N(yùn)ode用c++隱藏了,他也是異步的。
而我這篇文章想說明的是,如果再js端有花費(fèi)大量時(shí)間的運(yùn)算怎么辦。就用我上面所說的方法,用js打開c++的線程,這個(gè)subprocess.js,不在node的event序列內(nèi)部維護(hù)。是新的線程,因此不會(huì)阻塞其他的js function
相關(guān)文章
uni-app小程序中父組件和子組件傳值的實(shí)現(xiàn)實(shí)例
uniapp父子組件引用傳值,和vue的一樣,沒有小程序那樣的麻煩,下面這篇文章主要給大家介紹了關(guān)于uni-app小程序中父組件和子組件傳值的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
前端將dom轉(zhuǎn)換成圖片的方法(使用dom-to-image)
這篇文章主要介紹了使用輕量級(jí)的dom-to-image插件將DOM元素轉(zhuǎn)換為圖片并下載,該插件支持多種圖片格式,如SVG、PNG、JPEG等,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-01-01
layer子層給父層頁面元素賦值,以達(dá)到向父層頁面?zhèn)髦档男Ч麑?shí)例
下面小編就為大家?guī)硪黄猯ayer子層給父層頁面元素賦值,以達(dá)到向父層頁面?zhèn)髦档男Ч麑?shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
JavaScript中實(shí)現(xiàn)依賴注入的思路分享
這篇文章主要介紹了JavaScript中實(shí)現(xiàn)依賴注入的思路分享,本文給出的解決方案包括獲取func的參數(shù)列表、根據(jù)參數(shù)列表尋找依賴、傳遞依賴項(xiàng)參數(shù)并實(shí)例化等3個(gè)步驟,需要的朋友可以參考下2015-01-01
CryptoJS中AES實(shí)現(xiàn)前后端通用加解密技術(shù)
這篇文章主要為大家詳細(xì)介紹了CryptoJS中AES實(shí)現(xiàn)前后端通用加解密技術(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
ionic實(shí)現(xiàn)下拉刷新載入數(shù)據(jù)功能
這篇文章主要為大家詳細(xì)介紹了ionic實(shí)現(xiàn)下拉刷新載入數(shù)據(jù)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
前端使用國(guó)密sm2和sm4進(jìn)行加解密代碼示例
在前端實(shí)現(xiàn)加密算法時(shí),需要特別注意密鑰的安全存儲(chǔ)和管理,不應(yīng)將敏感的密鑰硬編碼在前端代碼中,這篇文章主要介紹了前端使用國(guó)密sm2和sm4進(jìn)行加解密的相關(guān)資料,需要的朋友可以參考下2025-04-04

