前言
最初,JavaScript是用於設計執行簡單的web任務的,比如表單驗證。直到2009年,Node.js的創建者Ryan Dahl讓開發人員認識到了通過JavaScript 進行後端開發已成爲可能,在後端開發中,用到最多的就是多線程以及線程之間的同步功能,今天小編就爲大家介紹一下如何使用Node.js實現多線程的應用。
Node.js的內部工作原理
在介紹之前,先給大家介紹一下Node.js的工作原理,Node.js基於單線程事件循環的範例進行操作。爲了充分掌握Node.js的功能,理解Node中線程(構成Node.js核心的事件循環)的概念至關重要。
Node.js中的線程
在Node.js中,線程是指單個進程內的獨立執行上下文,它是一個輕量級的處理單元,可以與同一進程中的其他線程併發操作。每個線程都有自己的執行指針和堆棧,並共享進程堆。
Node.js使用兩種類型的線程:由事件循環管理的主線程和工作池中的多個輔助線程。(在本文中”輔助線程“和"線程"可互換使用來指代工作線程)
Node.js中的主線程是Node.js啓動時的初始執行線程,它負責執行JavaScript代碼並處理傳入的請求,工作線程是與主線程並行運行的單獨執行線程。
Node.js 以多線程還是單線程方式運行?
“單線程”是指只有一個執行線程的程序,允許它順序執行任務,“多線程”意味着具有多個執行線程的程序可以同時執行任務。
通常情況下,Node.js 被認爲是單線程,因爲它只有一個處理 JavaScript 操作和 I/O 的主事件循環。然而,Node.js單線程架構中的主要元素是事件循環,這使得 Node.js 儘管是單線程運行,卻有着強大的性能。
事件循環
事件循環是一種註冊將要執行的回調(函數)的機制,並與 JavaScript 代碼在同一線程中運行。當 JavaScript 操作阻塞線程時,事件循環也會被阻塞。
工作池
工作池是一種執行模型,它生成並管理單獨的線程,這些線程同步執行任務並將結果返回到事件循環。然後,事件循環使用結果執行提供的回調。工作池主要用於異步 I/O 操作,例如與系統磁盤和網絡的交互,並在libuv中實現。儘管當 Node.js 需要在 JavaScript 和 C++ 之間進行內部通信時可能會出現輕微的延遲,但幾乎不會被注意到。
使用事件循環和工作池實現異步操作
藉助事件循環和工作池機制,能夠在 Node.js 中編寫有效處理異步操作的代碼。
fs.readFile(path.join(__dirname, './package.json'), (err, content) => {
if (err) {
return null;
}
console.log(content.toString());
});
介紹worker_threads模塊
worker_threads模塊是一個包,它允許在單核上創建多個線程,下面是worker_threads的使用方法:
type WorkerCallback = (err: any, result?: any) => any;
export function runWorker(path: string, cb: WorkerCallback, workerData: object | null = null) {
const worker = new Worker(path, { workerData });
worker.on('message', cb.bind(null, null));
worker.on('error', cb);
worker.on('exit', (exitCode) => {
if (exitCode === 0) {
return null;
}
return cb(new Error(`Worker has stopped with code ${exitCode}`));
});
return worker;
}
先創建一個Worker類的實例,第一個參數包含worker代碼的文件路徑,第二個參數應該是一個包含名爲workerData的屬性的對象,並在開始執行時能夠訪問的數據。
需要注意的是,無論是使用 JavaScript 還是TypeScript,文件路徑都應始終指向擴展名爲 .js 或.mjs的文件。
下面是一些常見的事件:
/*每當工作線程中發生未處理的異常時,會觸發錯誤事件。隨後,工作線程被終止,
並且可以將錯誤作爲提供的回調函數中的第一個參數進行訪問。這種設置可以實現及時捕獲和處理異常情況。
*/
worker.on('error', (error) => {});
/*
當工作線程退出時,會發出exit事件。如果調用process.exit(),exitCode將提供給回調函數。
如果使用worker.terminate()終止worker ,退出代碼將被設置爲1:
*/
worker.on('exit', (exitCode) => {});
/*
當工作線程向父線程發送數據時,會發出消息事件。現在,來看看數據是如何在線程之間共享的。
*/
worker.on('online', () => {});
使用工作線程的兩種方法:
工作程序
第一種方法是生成一個工作程序,執行其代碼,並將結果發送回父級。然而,這種方法具有顯着的開銷成本,包括創建新的工作線程、管理每個線程的內存開銷以及啓動和管理線程所需的資源。雖然可以使用這種方法完成任務,但它可能效率不高,尤其是在大規模基於節點的系統中。爲了解決與此方法相關的挑戰,通常採用第二種更常用的行業實踐。
工作線程池
第二種方法是實現工作線程池,它通過創建可重用於多個任務的工作線程池來減輕第一種方法的缺點。不是爲每個任務創建一個新的工作線程,而是創建一個工作線程池,並將任務分配給它們。
用技術術語來說,工作池可以被視爲管理工作線程池的抽象數據類型。池中的每個工作線程都被分配一個任務,並且該線程與其他線程並行執行該任務。
在工作池中分配任務的方式有多種,池充當管理器,將任務分配給工作線程,收集它們的結果,並促進池中線程之間的通信。
實現工作池可能涉及使用不同的數據結構和算法,例如任務隊列和消息傳遞系統。具體數據結構的選擇取決於多種因素,包括所需的工作線程數量、任務的性質以及線程之間所需的通信級別。
Node.js實現工作池
在 Node 中,可以使用內置功能或第三方工具來實現工作池。節點的內置工作線程模塊提供對工作線程的支持,可用於創建工作池。此外,還有多個庫可以通過爲工作線程提供高級 API 以及對任務調度和線程管理的額外支持來補充工作池。
這些庫自動執行任務調度和線程管理過程,從而更容易實現工作池。
爲了說明這一點,下面是一個利用Node內置工作線程功能的示例代碼:
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
// Main thread code
// Create an array to store worker threads
const workerThreads = [];
// Create a number of worker threads and add them to the array
for (let i = 0; i < 4; i++) {
workerThreads.push(new Worker(__filename));
}
// Send a message to each worker thread with a task to perform
workerThreads.forEach((worker, index) => {
worker.postMessage({ task: index });
});
} else {
// Worker thread code
// Listen for messages from the main thread
parentPort.on('message', message => {
console.log(`Worker ${process.pid}: Received task ${message.task}`);
// Perform the task
performTask(message.task);
});
function performTask(task) {
// … operations to be performed to execute the task
}
}
上面的代碼由兩部分組成:一部分用於主線程,另一部分用於工作線程。在主線程部分,從模塊中導入必要的成員,如果當前執行上下文在主線程中,則創建一個數組來存儲四個worker。隨後,帶有要執行的任務的新消息被髮送到每個工作線程。
在工作線程部分,使用屬性方法來監聽來自主線程的消息parentPort。一旦收到消息,記錄下進程ID和任務,並將任務傳遞給應用程序中適當的方法來執行。這樣就能夠更加智能地處理任務,並提供高效的函數執行。
使用線程的主要優點是什麼?
線程是一個強大的工具,可以極大地影響程序的性能、響應能力和整體效率。在 Node.js 中,線程對於開發人員來說是一項很有價值的功能,因爲它可以將進程拆分爲多個獨立的執行流。如果正確使用,線程可以提高程序的速度、效率和響應能力。
線程的優勢:
- 提高性能:線程允許併發執行多個任務,與順序運行任務相比,整體程序執行速度更快。
- 響應性:線程可以防止計算量大的任務阻塞或延遲其他操作的執行,確保程序保持對用戶輸入和其他任務的響應。
- 資源共享:Node.js 中的線程可以共享變量等資源,從而實現併發處理並加快程序執行速度。
- 易於編程:線程消除了 Node.js 中單線程架構的限制,使編程更加高效和可擴展。
- 提高可擴展性:線程可以輕鬆擴展,從而可以更輕鬆地構建高性能且可擴展的 Node.js 應用程序,這些應用程序可以輕鬆處理增加的負載。
結論
通過worker_threads模塊,可以輕鬆地將多線程支持集成到應用程序中。將密集的CPU計算卸載到單獨的線程中,可以大幅提高服務器的吞吐量。這種設計可以吸引更多來自人工智能、機器學習和大數據等領域的開發人員和工程師開始在他們的項目中使用Node.js。因此,使用worker_threads模塊是一種高效、便捷的方式來實現多線程編程。
Redis從入門到實踐
一節課帶你搞懂數據庫事務!
Chrome開發者工具使用教程
擴展鏈接: