创业笔记-Node.js入门之阻塞与非阻塞
阻塞與非阻塞
正如此前所提到的,當在請求處理程序中包括非阻塞操作時就會出問題。但是,在說這之前,我們先來看看什么是阻塞操作。
我不想去解釋“阻塞”和“非阻塞”的具體含義,我們直接來看,當在請求處理程序中加入阻塞操作時會發生什么。
這里,我們來修改下start請求處理程序,我們讓它等待10秒以后再返回“Hello Start”。因為,JavaScript中沒有類似sleep()這樣的操作,所以這里只能夠來點小Hack來模擬實現。
讓我們將requestHandlers.js修改成如下形式:
function start(){
? console.log("Request handler 'start' was called.");
? function sleep(milliSeconds){
? ? var startTime =newDate().getTime();
? ? while(newDate().getTime()< startTime + milliSeconds);
? }
? sleep(10000);
? return"Hello Start";
}
function upload(){
? console.log("Request handler 'upload' was called.");
? return"Hello Upload";
}
exports.start = start;
exports.upload = upload; 上述代碼中,當函數start()被調用的時候,Node.js會先等待10秒,之后才會返回“Hello Start”。當調用upload()的時候,會和此前一樣立即返回。
(當然了,這里只是模擬休眠10秒,實際場景中,這樣的阻塞操作有很多,比方說一些長時間的計算操作等。)
接下來就讓我們來看看,我們的改動帶來了哪些變化。
如往常一樣,我們先要重啟下服務器。為了看到效果,我們要進行一些相對復雜的操作(跟著我一起做): 首先,打開兩個瀏覽器窗口或者標簽頁。在第一個瀏覽器窗口的地址欄中輸入http://localhost:8888/start, 但是先不要打開它!
在第二個瀏覽器窗口的地址欄中輸入http://localhost:8888/upload, 同樣的,先不要打開它!
接下來,做如下操作:在第一個窗口中(“/start”)按下回車,然后快速切換到第二個窗口中(“/upload”)按下回車。
注意,發生了什么: /start URL加載花了10秒,這和我們預期的一樣。但是,/upload URL居然也花了10秒,而它在對應的請求處理程序中并沒有類似于sleep()這樣的操作!
這到底是為什么呢?原因就是start()包含了阻塞操作。形象的說就是“它阻塞了所有其他的處理工作”。
這顯然是個問題,因為Node一向是這樣來標榜自己的:“在node中除了代碼,所有一切都是并行執行的”。
這句話的意思是說,Node.js可以在不新增額外線程的情況下,依然可以對任務進行并行處理 —— Node.js是單線程的。它通過事件輪詢(event loop)來實現并行操作,對此,我們應該要充分利用這一點 —— 盡可能的避免阻塞操作,取而代之,多使用非阻塞操作。
然而,要用非阻塞操作,我們需要使用回調,通過將函數作為參數傳遞給其他需要花時間做處理的函數(比方說,休眠10秒,或者查詢數據庫,又或者是進行大量的計算)。
對于Node.js來說,它是這樣處理的:“嘿,probablyExpensiveFunction()(譯者注:這里指的就是需要花時間處理的函數),你繼續處理你的事情,我(Node.js線程)先不等你了,我繼續去處理你后面的代碼,請你提供一個callbackFunction(),等你處理完之后我會去調用該回調函數的,謝謝!”
(如果想要了解更多關于事件輪詢細節,可以閱讀Mixu的博文——理解node.js的事件輪詢。)
接下來,我們會介紹一種錯誤的使用非阻塞操作的方式。
和上次一樣,我們通過修改我們的應用來暴露問題。
這次我們還是拿start請求處理程序來“開刀”。將其修改成如下形式:
var exec = require("child_process").exec;
function start(){
? console.log("Request handler 'start' was called.");
? var content ="empty";
? exec("ls -lah",function(error, stdout, stderr){
? ? content = stdout;
? });
? return content;
}
function upload(){
? console.log("Request handler 'upload' was called.");
? return"Hello Upload";
}
exports.start = start;
exports.upload = upload; 上述代碼中,我們引入了一個新的Node.js模塊,child_process。之所以用它,是為了實現一個既簡單又實用的非阻塞操作:exec()。
exec()做了什么呢?它從Node.js來執行一個shell命令。在上述例子中,我們用它來獲取當前目錄下所有的文件(“ls -lah”),然后,當/startURL請求的時候將文件信息輸出到瀏覽器中。
上述代碼是非常直觀的: 創建了一個新的變量content(初始值為“empty”),執行“ls -lah”命令,將結果賦值給content,最后將content返回。
和往常一樣,我們啟動服務器,然后訪問“http://localhost:8888/start” 。
之后會載入一個漂亮的web頁面,其內容為“empty”。怎么回事?
這個時候,你可能大致已經猜到了,exec()在非阻塞這塊發揮了神奇的功效。它其實是個很好的東西,有了它,我們可以執行非常耗時的shell操作而無需迫使我們的應用停下來等待該操作。
(如果想要證明這一點,可以將“ls -lah”換成比如“find /”這樣更耗時的操作來效果)。
然而,針對瀏覽器顯示的結果來看,我們并不滿意我們的非阻塞操作,對吧?
好,接下來,我們來修正這個問題。在這過程中,讓我們先來看看為什么當前的這種方式不起作用。
問題就在于,為了進行非阻塞工作,exec()使用了回調函數。
在我們的例子中,該回調函數就是作為第二個參數傳遞給exec()的匿名函數:
function(error, stdout, stderr){
? content = stdout;
} 現在就到了問題根源所在了:我們的代碼是同步執行的,這就意味著在調用exec()之后,Node.js會立即執行 return content ;在這個時候,content仍然是“empty”,因為傳遞給exec()的回調函數還未執行到——因為exec()的操作是異步的。
我們這里“ls -lah”的操作其實是非常快的(除非當前目錄下有上百萬個文件)。這也是為什么回調函數也會很快的執行到 —— 不過,不管怎么說它還是異步的。
為了讓效果更加明顯,我們想象一個更耗時的命令: “find /”,它在我機器上需要執行1分鐘左右的時間,然而,盡管在請求處理程序中,我把“ls -lah”換成“find /”,當打開/start URL的時候,依然能夠立即獲得HTTP響應 —— 很明顯,當exec()在后臺執行的時候,Node.js自身會繼續執行后面的代碼。并且我們這里假設傳遞給exec()的回調函數,只會在“find /”命令執行完成之后才會被調用。
那究竟我們要如何才能實現將當前目錄下的文件列表顯示給用戶呢?
好,了解了這種不好的實現方式之后,我們接下來來介紹如何以正確的方式讓請求處理程序對瀏覽器請求作出響應。
以非阻塞操作進行請求響應
我剛剛提到了這樣一個短語 —— “正確的方式”。而事實上通常“正確的方式”一般都不簡單。
不過,用Node.js就有這樣一種實現方案: 函數傳遞。下面就讓我們來具體看看如何實現。
到目前為止,我們的應用已經可以通過應用各層之間傳遞值的方式(請求處理程序 -> 請求路由 -> 服務器)將請求處理程序返回的內容(請求處理程序最終要顯示給用戶的內容)傳遞給HTTP服務器。
現在我們采用如下這種新的實現方式:相對采用將內容傳遞給服務器的方式,我們這次采用將服務器“傳遞”給內容的方式。 從實踐角度來說,就是將response對象(從服務器的回調函數onRequest()獲取)通過請求路由傳遞給請求處理程序。 隨后,處理程序就可以采用該對象上的函數來對請求作出響應。
原理就是如此,接下來讓我們來一步步實現這種方案。
先從server.js開始:
var http = require("http");
var url = require("url");
function start(route, handle){
? function onRequest(request, response){
? ? var pathname = url.parse(request.url).pathname;
? ? console.log("Request for "+ pathname +" received.");
? ? route(handle, pathname, response);
? }
? http.createServer(onRequest).listen(8888);
? console.log("Server has started.");
}
exports.start = start; 相對此前從route()函數獲取返回值的做法,這次我們將response對象作為第三個參數傳遞給route()函數,并且,我們將onRequest()處理程序中所有有關response的函數調都移除,因為我們希望這部分工作讓route()函數來完成。
下面就來看看我們的router.js:
function route(handle, pathname, response){
? console.log("About to route a request for "+ pathname);
? if(typeof handle[pathname]==='function'){
? ? handle[pathname](response);
? }else{
? ? console.log("No request handler found for "+ pathname);
? ? response.writeHead(404,{"Content-Type":"text/plain"});
? ? response.write("404 Not found");
? ? response.end();
? }
}
exports.route = route; 同樣的模式:相對此前從請求處理程序中獲取返回值,這次取而代之的是直接傳遞response對象。
如果沒有對應的請求處理器處理,我們就直接返回“404”錯誤。
最后,我們將requestHandler.js修改為如下形式:
var exec = require("child_process").exec;
function start(response){
? console.log("Request handler 'start' was called.");
? exec("ls -lah",function(error, stdout, stderr){
? ? response.writeHead(200,{"Content-Type":"text/plain"});
? ? response.write(stdout);
? ? response.end();
? });
}
function upload(response){
? console.log("Request handler 'upload' was called.");
? response.writeHead(200,{"Content-Type":"text/plain"});
? response.write("Hello Upload");
? response.end();
}
exports.start = start;
exports.upload = upload; 我們的處理程序函數需要接收response參數,為了對請求作出直接的響應。
start處理程序在exec()的匿名回調函數中做請求響應的操作,而upload處理程序仍然是簡單的回復“Hello World”,只是這次是使用response對象而已。
這時再次我們啟動應用(node index.js),一切都會工作的很好。
如果想要證明/start處理程序中耗時的操作不會阻塞對/upload請求作出立即響應的話,可以將requestHandlers.js修改為如下形式:
var exec = require("child_process").exec;
function start(response){
? console.log("Request handler 'start' was called.");
? exec("find /",
? ? { timeout:10000, maxBuffer:20000*1024},
? ? function(error, stdout, stderr){
? ? ? response.writeHead(200,{"Content-Type":"text/plain"});
? ? ? response.write(stdout);
? ? ? response.end();
? ? });
}
function upload(response){
? console.log("Request handler 'upload' was called.");
? response.writeHead(200,{"Content-Type":"text/plain"});
? response.write("Hello Upload");
? response.end();
}
exports.start = start;
exports.upload = upload; 這樣一來,當請求http://localhost:8888/start的時候,會花10秒鐘的時間才載入,而當請求http://localhost:8888/upload的時候,會立即響應,縱然這個時候/start響應還在處理中。
轉載于:https://www.cnblogs.com/hznet/p/5239905.html
總結
以上是生活随笔為你收集整理的创业笔记-Node.js入门之阻塞与非阻塞的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黄山风景区对医护人员免费吗
- 下一篇: 补车漆多少钱啊?