Node.js 多進(jìn)程處理CPU密集任務(wù)的實(shí)現(xiàn)
Node.js 單線(xiàn)程與多進(jìn)程
大家都知道 Node.js 性能很高,是以異步事件驅(qū)動(dòng)、非阻塞 I/O 而被廣泛使用。但缺點(diǎn)也很明顯,由于 Node.js 是單線(xiàn)程程序,如果長(zhǎng)時(shí)間運(yùn)算,會(huì)導(dǎo)致 CPU 不能及時(shí)釋放,所以并不適合 CPU 密集型應(yīng)用。
當(dāng)然,也不是沒(méi)有辦法解決這個(gè)問(wèn)題。雖然 Node.js 不支持多線(xiàn)程,但是可創(chuàng)建多子進(jìn)程來(lái)執(zhí)行任務(wù)。
Node.js 提供了 child_process 和 cluster 兩個(gè)模塊可用于創(chuàng)建多子進(jìn)程
下面我們就分別使用單線(xiàn)程和多進(jìn)程來(lái)模擬查找大量斐波那契數(shù)進(jìn)行 CPU 密集測(cè)試
以下代碼是查找 500 次位置為 35 的斐波那契數(shù)(方便測(cè)試,定了一個(gè)時(shí)間不需要太長(zhǎng)也不會(huì)太短的位置)
單線(xiàn)程處理
代碼:single.js
function fibonacci(n) {
if (n == 0 || n == 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
let startTime = Date.now();
let totalCount = 500;
let completedCount = 0;
let n = 35;
for (let i = 0; i < totalCount; i++) {
fibonacci(n);
completedCount++;
console.log(`process: ${completedCount}/${totalCount}`);
}
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
console.info(`任務(wù)完成,用時(shí): ${Date.now() - startTime}ms`);
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
執(zhí)行node single.js 查看結(jié)果
在我的電腦上顯示結(jié)果為44611ms(電腦配置不同也會(huì)有差異)。
...
process: 500/500
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏
任務(wù)完成,用時(shí): 44611ms
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏
查找 500 次需要 44 秒,太慢了??上攵绻恢酶?,數(shù)量更多...
那我們來(lái)嘗試用多進(jìn)程試試 ⬇️
多進(jìn)程
采用 cluster 模塊,Master-Worker 模式來(lái)測(cè)試
共 3 個(gè) js,分別為主線(xiàn)程代碼:master.js、子進(jìn)程代碼:worker.js、入口代碼:cluster.js(入口可無(wú)需單獨(dú)寫(xiě)一個(gè) js、這里是為了看起來(lái)更清楚一些)
主線(xiàn)程代碼:master.js
const cluster = require("cluster");
const numCPUs = require("os").cpus().length;
// 設(shè)置子進(jìn)程執(zhí)行程序
cluster.setupMaster({
exec: "./worker.js",
slient: true
});
function run() {
// 記錄開(kāi)始時(shí)間
const startTime = Date.now();
// 總數(shù)
const totalCount = 500;
// 當(dāng)前已處理任務(wù)數(shù)
let completedCount = 0;
// 任務(wù)生成器
const fbGenerator = FbGenerator(totalCount);
if (cluster.isMaster) {
cluster.on("fork", function(worker) {
console.log(`[master] : fork worker ${worker.id}`);
});
cluster.on("exit", function(worker, code, signal) {
console.log(`[master] : worker ${worker.id} died`);
});
for (let i = 0; i < numCPUs; i++) {
const worker = cluster.fork();
// 接收子進(jìn)程數(shù)據(jù)
worker.on("message", function(msg) {
// 完成一個(gè),記錄并打印進(jìn)度
completedCount++;
console.log(`process: ${completedCount}/${totalCount}`);
nextTask(this);
});
nextTask(worker);
}
} else {
process.on("message", function(msg) {
console.log(msg);
});
}
/**
* 繼續(xù)下一個(gè)任務(wù)
*
* @param {ChildProcess} worker 子進(jìn)程對(duì)象,將在此進(jìn)程上執(zhí)行本次任務(wù)
*/
function nextTask(worker) {
// 獲取下一個(gè)參數(shù)
const data = fbGenerator.next();
// 判斷是否已經(jīng)完成,如果完成則調(diào)用完成函數(shù),結(jié)束程序
if (data.done) {
done();
return;
}
// 否則繼續(xù)任務(wù)
// 向子進(jìn)程發(fā)送數(shù)據(jù)
worker.send(data.value);
}
/**
* 完成,當(dāng)所有任務(wù)完成時(shí)調(diào)用該函數(shù)以結(jié)束程序
*/
function done() {
if (completedCount >= totalCount) {
cluster.disconnect();
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
console.info(`任務(wù)完成,用時(shí): ${Date.now() - startTime}ms`);
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
}
}
}
/**
* 生成器
*/
function* FbGenerator(count) {
var n = 35;
for (var i = 0; i < count; i++) {
yield n;
}
return;
}
module.exports = {
run
};
1.這里是根據(jù)當(dāng)前電腦的邏輯 CPU 核數(shù)來(lái)創(chuàng)建子進(jìn)程的,不同電腦數(shù)量也會(huì)不一樣,我的 CPU 是 6 個(gè)物理核數(shù),由于支持超線(xiàn)程處理,所以邏輯核數(shù)是 12,故會(huì)創(chuàng)建出 12 個(gè)子進(jìn)程
2.主線(xiàn)程與子進(jìn)程之間通信是通過(guò)send方法來(lái)發(fā)送數(shù)據(jù),監(jiān)聽(tīng)message事件來(lái)接收數(shù)據(jù)
3.不知道大家有沒(méi)有注意到我這里使用了 ES6 的 Generator 生成器來(lái)模擬生成每次需要查找的斐波那契數(shù)位置(雖然是寫(xiě)死的 😂,為了和上面的單線(xiàn)程保證統(tǒng)一)。這么做是為了不讓所有任務(wù)一次性扔出去,因?yàn)榫退闳映鋈ヒ矔?huì)被阻塞,還不如放在程序端就給控制住,完成一個(gè),放一個(gè)。
子進(jìn)程代碼:worker.js
function fibonacci(n) {
if (n == 0 || n == 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
// 接收主線(xiàn)程發(fā)送過(guò)來(lái)的任務(wù),并開(kāi)始查找斐波那契數(shù)
process.on("message", n => {
var res = fibonacci(n);
// 查找結(jié)束后通知主線(xiàn)程,以便主線(xiàn)程再度進(jìn)行任務(wù)分配
process.send(res);
});
入口代碼:cluster.js
// 引入主線(xiàn)程js,并執(zhí)行暴露出來(lái)的run方法
const master = require("./master");
master.run();
執(zhí)行node cluster.js 查看結(jié)果
在我的電腦上顯示結(jié)果為10724ms(電腦配置不同也會(huì)有差異)。
process: 500/500
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏
任務(wù)完成,用時(shí): 10724ms
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏
結(jié)果
進(jìn)過(guò)上面兩種方式的對(duì)比,結(jié)果很明顯,多進(jìn)程處理速度是單線(xiàn)程處理速度的 4 倍多。而且有條件的情況下,如果電腦 CPU 足夠,進(jìn)程數(shù)更多,那么速度也會(huì)更快。
如果有更好的方案或別的語(yǔ)言能處理你的需求那就更好,誰(shuí)讓 Node.js 天生就不適合 CPU 密集型應(yīng)用呢。。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Windows 系統(tǒng)下設(shè)置Nodejs NPM全局路徑
這篇文章主要介紹了Windows 系統(tǒng)下設(shè)置Nodejs NPM全局路徑2016-04-04
詳解NodeJS框架express的路徑映射(路由)功能及控制
這篇文章主要介紹了詳解NodeJS框架express的路徑映射(路由)功能及控制,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
使用nvm安裝node的過(guò)程及注意事項(xiàng)
在學(xué)習(xí)vue的過(guò)程中,最開(kāi)始的就是要先安裝nodejs環(huán)境,此處記錄安裝過(guò)程及注意事項(xiàng),首先需要先卸載程序中卸載先前安裝過(guò)的nvm,刪除文件管理器中安裝的文件,感興趣的朋友跟隨小編一起看看吧2024-06-06
node.js中的fs.fchown方法使用說(shuō)明
這篇文章主要介紹了node.js中的fs.fchown方法使用說(shuō)明,本文介紹了fs.fchown方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12
淺談Node.js輕量級(jí)Web框架Express4.x使用指南
本篇文章主要介紹了淺談Node.js輕量級(jí)Web框架Express4.x使用指南,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
基于NodeJS的前后端分離的思考與實(shí)踐(六)Nginx + Node.js + Java 的軟件棧部署實(shí)踐
關(guān)于前后端分享的思考,我們已經(jīng)有五篇文章闡述思路與設(shè)計(jì)。本文介紹淘寶網(wǎng)收藏夾將 Node.js 引入傳統(tǒng)技術(shù)棧的具體實(shí)踐。2014-09-09

