很多人都想知道單線程的 Node.js 怎么能與多線程后端競爭。考慮到其所謂的單線程特性,許多大公司選擇 Node 作為其后端似乎違反直覺。要想知道原因,必須理解其單線程的真正含義。
JavaScript 的設計非常適合在網上做比較簡單的事情,比如驗證表單,或者說創建彩虹色的鼠標軌跡。 在2009年,Node.js的創始人 Ryan Dahl使開發人員可以用該語言編寫后端代碼。
通常支持多線程的后端語言具有各種機制,用于在線程和其他面向線程的功能之間同步數據。要向 JavaScript 添加對此類功能的支持,需要修改整個語言,這不是 Dahl 的目標。為了讓純 JavaScript 支持多線程,他必須想一個變通方法。接下來讓我們探索一下其中的奧秘……
Node.js 是如何工作的
Node.js 使用兩種線程:event loop 處理的主線程和 worker pool 中的幾個輔助線程。
事件循環是一種機制,它采用回調(函數)并注冊它們,準備在將來的某個時刻執行。它與相關的 JavaScript 代碼在同一個線程中運行。當 JavaScript 操作阻塞線程時,事件循環也會被阻止。
工作池是一種執行模型,它產生并處理單獨的線程,然后同步執行任務,并將結果返回到事件循環。事件循環使用返回的結果執行提供的回調。
簡而言之,它負責異步 I/O操作 —— 主要是與系統磁盤和網絡的交互。它主要由諸如 fs(I/O 密集)或 crypto(CPU 密集)等模塊使用。工作池用 libuv 實現,當 Node 需要在 JavaScript 和 C++ 之間進行內部通信時,會導致輕微的延遲,但這幾乎不可察覺。
基于這兩種機制,我們可以編寫如下代碼:
fs.readFile(path.join(__dirname, './package.json'), (err, content) => { if (err) { return null; } console.log(content.toString());});前面提到的 fs 模塊告訴工作池使用其中一個線程來讀取文件的內容,并在完成后通知事件循環。然后事件循環獲取提供的回調函數,并用文件的內容執行它。
以上是非阻塞代碼的示例,我們不必同步等待某事的發生。只需告訴工作池去讀取文件,并用結果去調用提供的函數即可。由于工作池有自己的線程,因此事件循環可以在讀取文件時繼續正常執行。
在不需要同步執行某些復雜操作時,這一切都相安無事:任何運行時間太長的函數都會阻塞線程。如果應用程序中有大量這類功能,就可能會明顯降低服務器的吞吐量,甚至完全凍結它。在這種情況下,無法繼續將工作委派給工作池。
在需要對數據進行復雜的計算時(如AI、機器學習或大數據)無法真正有效地使用 Node.js,因為操作阻塞了主(且唯一)線程,使服務器無響應。在 Node.js v10.5.0 發布之前就是這種情況,在這一版本增加了對多線程的支持。
新聞熱點
疑難解答
圖片精選