一、簡述
這片文章主要講述Nodejs中的阻塞和非阻塞回調,涉及到事件循環和libuv。但是你並不需要提前瞭解這些知識,你只需要具備js和Nodejs回調模式的基礎。
"I/O"指的是與系統硬盤的交互以及libuv所支持的網絡交互。
1.Blocking阻塞
1.1代碼對比
阻塞指的是Nodejs中的js進程必須等待非js操作完成之後才能執行,原因是當阻塞操作發生時,事件循環無法持續運行。
在nodejs中,由於cpu內存不夠而造成js運行性能差的情況不叫阻塞,只有js在等待非js操作(例如:I/O)完成時才運行的情況叫做阻塞。Nodejs標準庫中的同步方法是常用的阻塞操作。原生模塊中也有阻塞方法。
Nodejs標準庫中的所有IO方法都提供了異步非阻塞的版本,這些方法接收回調函數。一些方法有其對應的阻塞版本,以Sync爲開頭命名。
阻塞方法同步執行,而非阻塞方法異步執行。以文件系統模塊爲例,下面是一個同步文件讀取方法:
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
下面是與之對應的異步寫法:const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
});
阻塞方法相對非阻塞方法來說比較簡單,但是,有其缺點:它會等到文件讀取完成後才執行後續操作。需要注意的是,在阻塞方法中,錯誤必須被捕獲,否則程序就會崩潰。但是在非阻塞版本中,可以不進行錯誤的捕獲。
2.併發性和吞吐量(Concurrency and Throughput)
Nodejs中執行js是單線程的,所以,併發性指的是執行事件回調函數的能力。併發性強的代碼即使在非js操作發生的時候,也能繼續執行事件回調。舉例:每一個到達服務器的請求會花費50ms完成,其中的45ms用來對數據庫的異步讀取。選擇非阻塞的異步方法可以釋放服務器45ms的時間去處理其他請求。這是選擇非阻塞方法的最明顯的優勢。
js中的事件循環模型與其他語言不同的地方在於:其他語言可以使用多線程來處理併發性工作。
3.混合阻塞方法和非阻塞方法的危險之處
當處理IO時有一些需要避免的模式:
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.unlinkSync('/file.md');
在上面的代碼中,fs.unlinkSync()得會在fs.readFile方法之前運行,這樣就會導致,文件在沒有讀取完成前被刪除。最好的方法如下:
const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr;
console.log(data);
fs.unlink('/file.md', (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
});