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

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

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

這篇博客將介紹Node.js的阻塞(Blocking)與非阻塞(Non-Blocking)。我會提到Event Loop與libuv,可是不瞭解它們也不會影響閱讀。讀者只須要有必定的JavaScript基礎,理解Node.js的回調函數(callback pattern)就能夠了。數據庫

博客中提到了不少次I/O,它主要指的是使用libuv與系統的磁盤與網絡進行交互。小程序

阻塞(Blocking)

阻塞指的是一部分Node.js代碼須要等到一些非Node.js代碼執行完成以後才能繼續執行。這是由於當阻塞發生時,Event Loop沒法繼續執行。微信小程序

對於Node.js來講,因爲CPU密集的操做致使代碼性能不好時,不能稱爲阻塞。當須要等待非Node.js代碼執行時,才能稱爲阻塞。Node.js中依賴於libuv的同步方法(以Sync結尾)致使阻塞,是最多見的狀況。固然,一些不依賴於libuv的原生Node.js方法有些也能致使阻塞。api

Node.js中全部與I/O相關的方法都提供了異步版本,它們是非阻塞的,能夠指定回調函數,例如fs.readFile。其中一些方法也有對應的阻塞版本,它們的函數名以Sync結尾,例如fs.readFileSync服務器

代碼示例

阻塞的方法是同步執行的,而非阻塞的方法是異步執行。微信

以讀文件爲例,下面是同步執行的代碼:網絡

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

對應的異步代碼以下:併發

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
}); // 代碼不會由於讀文件阻塞,會繼續執行後面的代碼
console.log("Hello, Fundebug!"); // 文件讀完以前就會打印

第一個示例代碼看起來要簡單不少,可是它的缺點是會阻塞代碼執行,後面的代碼須要等到整個文件讀取完成以後才能繼續執行。

在同步代碼中,若是讀取文件出錯了,則錯誤須要使用try...catch處理,不然進程會崩潰。對於異步代碼,是否處理回調函數的錯誤則取決於開發者。

咱們能夠將示例代碼稍微修改一下,下面是同步代碼:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); 
console.log(data);
moreWork(); // console.log以後再執行

異步代碼以下:

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

在第一個示例中,console.log將會先於moreWork()執行。在第二個示例中,因爲fs.readFile()是非阻塞的,代碼能夠繼續執行,所以moreWork()會先於console.log執行。

moreWork()不用等待讀取整個文件,能夠繼續執行,這是Node.js能夠增長吞吐量的關鍵。

併發與吞吐量

Node.js中JS代碼執行是單線程的,所以併發指的是Event Loop能夠在執行其餘代碼以後再去執行回調函數。若是但願代碼能夠併發執行,則全部非JavaScript代碼好比I/O執行時,必須保證Event Loop繼續運行。

舉個例子,假設Web服務器的每一個請求須要50ms完成,其中45ms是數據庫的I/O操做。若是使用非阻塞的異步方式執行數據庫I/O的話,則能夠節省45ms來處理其餘請求,這能夠極大地提升系統的吞吐量。

Event Loop這種方式與其餘許多語言都不同,一般它們會建立新的線程來處理併發。

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

當咱們處理I/O時,應該避免如下代碼:

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

爲了不這種狀況,咱們應該是要非阻塞方式,來保證它們按照正確的順序執行。

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()的回調函數中。

參考

關於Fundebug

Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟件、百姓網等衆多品牌企業。歡迎你們免費試用

版權聲明

轉載時請註明做者Fundebug以及本文地址:
https://blog.fundebug.com/2019/06/12/overview-of-nodejs-blocking-vs-non-blocking/

相關文章
相關標籤/搜索