Node.js官方文檔:到底什么是阻塞(Blocking)與非阻塞(Non-Blocking)?

譯者按: Node.js文檔閱讀系列之一。

為了保證可讀性,本文采用意譯而非直譯。

這篇博客將介紹Node.js的阻塞(Blocking)與非阻塞(Non-Blocking)。我會提到Event Loop與libuv,但是不了解它們也不會影響閱讀。讀者只需要有一定的JavaScript基礎(chǔ),理解Node.js的回調(diào)函數(shù)(callback pattern)就可以了。

博客中提到了很多次I/O,它主要指的是使用libuv與系統(tǒng)的磁盤與網(wǎng)絡(luò)進(jìn)行交互。

阻塞(Blocking)

阻塞指的是一部分Node.js代碼需要等到一些非Node.js代碼執(zhí)行完成之后才能繼續(xù)執(zhí)行。這是因為當(dāng)阻塞發(fā)生時,Event Loop無法繼續(xù)執(zhí)行。

對于Node.js來說,由于CPU密集的操作導(dǎo)致代碼性能很差時,不能稱為阻塞。當(dāng)需要等待非Node.js代碼執(zhí)行時,才能稱為阻塞。Node.js中依賴于libuv的同步方法(以Sync結(jié)尾)導(dǎo)致阻塞,是最常見的情況。當(dāng)然,一些不依賴于libuv的原生Node.js方法有些也能導(dǎo)致阻塞。

Node.js中所有與I/O相關(guān)的方法都提供了異步版本,它們是非阻塞的,可以指定回調(diào)函數(shù),例如fs.readFile。其中一些方法也有對應(yīng)的阻塞版本,它們的函數(shù)名以Sync結(jié)尾,例如fs.readFileSync。

代碼示例

阻塞的方法是同步執(zhí)行的,而非阻塞的方法是異步執(zhí)行。

以讀文件為例,下面是同步執(zhí)行的代碼:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 文件讀取完成之前,代碼會阻塞,不會執(zhí)行后面的代碼
console.log("Hello, Fundebug!"); // 文件讀取完成之后才會打印

對應(yīng)的異步代碼如下:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
}); // 代碼不會因為讀文件阻塞,會繼續(xù)執(zhí)行后面的代碼
console.log("Hello, Fundebug!"); // 文件讀完之前就會打印

第一個示例代碼看起來要簡單很多,但是它的缺點是會阻塞代碼執(zhí)行,后面的代碼需要等到整個文件讀取完成之后才能繼續(xù)執(zhí)行。

在同步代碼中,如果讀取文件出錯了,則錯誤需要使用try...catch處理,否則進(jìn)程會崩潰。對于異步代碼,是否處理回調(diào)函數(shù)的錯誤則取決于開發(fā)者。

我們可以將示例代碼稍微修改一下,下面是同步代碼:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); 
console.log(data);
moreWork(); // console.log之后再執(zhí)行

異步代碼如下:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
moreWork(); // 先于console.log執(zhí)行

在第一個示例中,console.log將會先于moreWork()執(zhí)行。在第二個示例中,由于fs.readFile()是非阻塞的,代碼可以繼續(xù)執(zhí)行,因此moreWork()會先于console.log執(zhí)行。

moreWork()不用等待讀取整個文件,可以繼續(xù)執(zhí)行,這是Node.js可以增加吞吐量的關(guān)鍵。

并發(fā)與吞吐量

Node.js中JS代碼執(zhí)行是單線程的,因此并發(fā)指的是Event Loop可以在執(zhí)行其他代碼之后再去執(zhí)行回調(diào)函數(shù)。如果希望代碼可以并發(fā)執(zhí)行,則所有非JavaScript代碼比如I/O執(zhí)行時,必須保證Event Loop繼續(xù)運行。

舉個例子,假設(shè)Web服務(wù)器的每個請求需要50ms完成,其中45ms是數(shù)據(jù)庫的I/O操作。如果使用非阻塞的異步方式執(zhí)行數(shù)據(jù)庫I/O的話,則可以節(jié)省45ms來處理其他請求,這可以極大地提高系統(tǒng)的吞吐量。

Event Loop這種方式與其他許多語言都不一樣,通常它們會創(chuàng)建新的線程來處理并發(fā)。

混用阻塞與非阻塞代碼會出問題

當(dāng)我們處理I/O時,應(yīng)該避免以下代碼:

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()之前執(zhí)行,也就是說,我們在讀取file.md之前,這個文件就已經(jīng)被刪掉了。

為了避免這種情況,我們應(yīng)該是要非阻塞方式,來保證它們按照正確的順序執(zhí)行。

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;
  });
});

上面的示例中,我們把非阻塞的fs.unlink()放在fs.readFile()的回調(diào)函數(shù)中。

參考

關(guān)于Fundebug

Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應(yīng)用實時BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟件、百姓網(wǎng)等眾多品牌企業(yè)。歡迎大家免費試用!

版權(quán)聲明

轉(zhuǎn)載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2019/06/12/overview-of-nodejs-blocking-vs-non-blocking/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容