這篇文章主要內容爲node.js
中阻塞和非阻塞的區別。其中,eventloop
和libuv
會被引用到,不過並不須要你以前就瞭解過這些知識。咱們假設本文的讀者至少對Javascript
和node.js
回調設計模式要有基本的理解和掌握。javascript
`I/O`在本文中主要指的是由`libuv`提供支持的與磁盤和網絡發生的交互。
阻塞指的是node.js
中額外的javascript
操做必須等待一個非node.js
的操做完成才能執行。這種現象發生的緣由是當一個阻塞操做發生時,eventloop
沒法繼續執行javascript
代碼。
在Node.js
中,如Javascript
所表現出的低性能是因爲CPU密集計算而不是等待一個非Javascript
操做,好比I/O
,這並非一般來講的阻塞操做。Node.js
標準庫中使用了libuv
的同步方法纔是廣泛意義上的阻塞操做。本地模塊也會有阻塞方法。
全部Node.js
標準庫中的I/O
都提供了異步的版本,來實現非阻塞,而且接受回調函數。一些方法還提供了同步的版本,並以Sync
來做爲後綴。java
阻塞
方法以同步的方式執行,非阻塞
方法以異步的方式執行。
咱們以File System
這個模塊舉個例子,這是一個同步的文件讀取操做:node
const fs = require('fs'); const data = fs.readFileSync('/file.md'); // 在這裏發生阻塞直到文件讀取完畢
下面是一個異步的文件讀取操做:數據庫
const fs = require('fs'); fs.readFile('/file.md', (err, data) => { if (err) throw err; });
第一個例子和第二個例子比較起來很類似,可是卻因爲第二行阻塞了其它javascript
代碼的執行直到文件讀取完爲止顯現出本身的缺點所在。請記住,在同步執行的版本里,若是一個error
被拋出的話,須要咱們去捕獲它,不然程序將會crash掉。在異步執行的版本里,則是取決於咱們本身是否來拋出這個異常。設計模式
讓咱們繼續在前面的例子上延伸,下面是同步的版本:服務器
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
方法。執行moreWork
方法而沒必要等到文件讀取完畢的這種能力是容許更高吞吐量的一個關鍵設計選擇。併發
Node.js
中的JavasScript
執行是單線程的,所以併發是指事件循環在完成其餘工做後執行JavaScript
回調函數的能力。任何將要以並行的方式運行的代碼必須容許javascript
操做的事件循環繼續運行,如I/O
,正在發生。
舉個栗子,咱們考慮如下這種狀況,發送到WEB服務器的每一個請求須要50ms來完成,其中45ms是能夠異步執行的數據庫I/O
操做。對服務器來講使用非阻塞異步操做的話能夠在每一個請求身上節約45ms來處理其餘的請求。僅僅將阻塞操做替換成了非阻塞操做就能夠產生如此巨大的區別。eventloop
不像其餘語言中能夠有額外的線程來處理並行任務。異步
在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.readF ile()
以前就執行了,這就會致使一個嚴重的問題,在讀取文件前就從內存中刪除了該文件。一個更好的方式是將刪除操做非阻塞化,而且保證正確的執行順序,以下所示:
const fs = require('fs'); fs.readFile('/file.md', (err, data) => { if (err) throw err; console.log(data); fs.unlink('/file.md', (err) => { if (err) throw err; }); });
上面的代碼經過在fs.readFile
的callback
中加入fs.unlink
的代碼來保證操做的正確順序。