Node.js 多进程处理CPU密集任务的实现


Posted in Javascript onMay 26, 2019

Node.js 单线程与多进程

大家都知道 Node.js 性能很高,是以异步事件驱动、非阻塞 I/O 而被广泛使用。但缺点也很明显,由于 Node.js 是单线程程序,如果长时间运算,会导致 CPU 不能及时释放,所以并不适合 CPU 密集型应用。

当然,也不是没有办法解决这个问题。虽然 Node.js 不支持多线程,但是可创建多子进程来执行任务。
Node.js 提供了 child_process 和 cluster 两个模块可用于创建多子进程

下面我们就分别使用单线程和多进程来模拟查找大量斐波那契数进行 CPU 密集测试

以下代码是查找 500 次位置为 35 的斐波那契数(方便测试,定了一个时间不需要太长也不会太短的位置)

单线程处理

代码: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(`任务完成,用时: ${Date.now() - startTime}ms`);
console.log("? ? ? ? ? ? ? ? ? ?");

执行node single.js 查看结果

在我的电脑上显示结果为44611ms(电脑配置不同也会有差异)。

...
process: 500/500
? ? ? ? ? ? ? ? ? ?
任务完成,用时: 44611ms
? ? ? ? ? ? ? ? ? ?

查找 500 次需要 44 秒,太慢了。可想而知如果位置更大,数量更多...

那我们来尝试用多进程试试 ⬇️

多进程

采用 cluster 模块,Master-Worker 模式来测试

共 3 个 js,分别为主线程代码:master.js、子进程代码:worker.js、入口代码:cluster.js(入口可无需单独写一个 js、这里是为了看起来更清楚一些)

主线程代码:master.js

const cluster = require("cluster");
const numCPUs = require("os").cpus().length;

// 设置子进程执行程序
cluster.setupMaster({
 exec: "./worker.js",
 slient: true
});

function run() {
 // 记录开始时间
 const startTime = Date.now();
 // 总数
 const totalCount = 500;
 // 当前已处理任务数
 let completedCount = 0;
 // 任务生成器
 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();

   // 接收子进程数据
   worker.on("message", function(msg) {
    // 完成一个,记录并打印进度
    completedCount++;
    console.log(`process: ${completedCount}/${totalCount}`);

    nextTask(this);
   });

   nextTask(worker);
  }
 } else {
  process.on("message", function(msg) {
   console.log(msg);
  });
 }

 /**
  * 继续下一个任务
  *
  * @param {ChildProcess} worker 子进程对象,将在此进程上执行本次任务
  */
 function nextTask(worker) {
  // 获取下一个参数
  const data = fbGenerator.next();
  // 判断是否已经完成,如果完成则调用完成函数,结束程序
  if (data.done) {
   done();
   return;
  }
  // 否则继续任务
  // 向子进程发送数据
  worker.send(data.value);
 }

 /**
  * 完成,当所有任务完成时调用该函数以结束程序
  */
 function done() {
  if (completedCount >= totalCount) {
   cluster.disconnect();
   console.log("? ? ? ? ? ? ? ? ? ?");
   console.info(`任务完成,用时: ${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.这里是根据当前电脑的逻辑 CPU 核数来创建子进程的,不同电脑数量也会不一样,我的 CPU 是 6 个物理核数,由于支持超线程处理,所以逻辑核数是 12,故会创建出 12 个子进程

2.主线程与子进程之间通信是通过send方法来发送数据,监听message事件来接收数据

3.不知道大家有没有注意到我这里使用了 ES6 的 Generator 生成器来模拟生成每次需要查找的斐波那契数位置(虽然是写死的 ?,为了和上面的单线程保证统一)。这么做是为了不让所有任务一次性扔出去,因为就算扔出去也会被阻塞,还不如放在程序端就给控制住,完成一个,放一个。

子进程代码:worker.js

function fibonacci(n) {
 if (n == 0 || n == 1) {
  return n;
 } else {
  return fibonacci(n - 1) + fibonacci(n - 2);
 }
}

// 接收主线程发送过来的任务,并开始查找斐波那契数
process.on("message", n => {
 var res = fibonacci(n);
 // 查找结束后通知主线程,以便主线程再度进行任务分配
 process.send(res);
});
入口代码:cluster.js
// 引入主线程js,并执行暴露出来的run方法
const master = require("./master");
master.run();

执行node cluster.js 查看结果

在我的电脑上显示结果为10724ms(电脑配置不同也会有差异)。

process: 500/500
? ? ? ? ? ? ? ? ? ?
任务完成,用时: 10724ms
? ? ? ? ? ? ? ? ? ?

结果

进过上面两种方式的对比,结果很明显,多进程处理速度是单线程处理速度的 4 倍多。而且有条件的情况下,如果电脑 CPU 足够,进程数更多,那么速度也会更快。

如果有更好的方案或别的语言能处理你的需求那就更好,谁让 Node.js 天生就不适合 CPU 密集型应用呢。。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS 面向对象的5钟写法
Jul 31 Javascript
JS构建页面的DOM节点结构的实现代码
Dec 09 Javascript
jQuery实现id模糊查询的小例子
Mar 19 Javascript
运用JQuery的toggle实现网页加载完成自动弹窗
Mar 18 Javascript
jQuery遍历之next()、nextAll()方法使用实例
Nov 08 Javascript
鼠标经过子元素触发mouseout,mouseover事件的解决方案
Jul 26 Javascript
JS获取checkbox的个数简单实例
Aug 19 Javascript
使用jQuery操作DOM的方法小结
Feb 27 Javascript
js canvas实现简单的图像扩散效果
Jun 28 Javascript
js前端面试之同步与异步问题详解
Apr 03 Javascript
angular异步验证防抖踩坑实录
Dec 01 Javascript
基于vue中的scoped坑点解说
Sep 04 Javascript
小程序封装路由文件和路由方法(5种全解析)
May 26 #Javascript
vue2.0 实现富文本编辑器功能
May 26 #Javascript
富文本编辑器vue2-editor实现全屏功能
May 26 #Javascript
vue中$refs, $emit, $on, $once, $off的使用详解
May 26 #Javascript
我要点爆”微信小程序云开发之项目建立与我的页面功能实现
May 26 #Javascript
vue指令做滚动加载和监听等
May 26 #Javascript
vxe-table vue table 表格组件功能
May 26 #Javascript
You might like
thinkPHP使用post方式查询时分页失效的解决方法
2015/12/09 PHP
javascript 动态数据下的锚点错位问题解决方法
2008/12/24 Javascript
JS中==与===操作符的比较
2009/03/21 Javascript
元素的内联事件处理函数的特殊作用域在各浏览器中存在差异
2011/01/12 Javascript
点击显示指定元素隐藏其他同辈元素的方法
2014/02/19 Javascript
基于jQuery实现仿51job城市选择功能实例代码
2016/03/02 Javascript
谈谈PHP中相对路径的问题与绝对路径的使用
2016/08/16 Javascript
微信开发 JS-SDK 6.0.2 经常遇到问题总结
2016/12/08 Javascript
bootstrap3-dialog-master模态框使用详解
2017/08/22 Javascript
JavaScript实现获取select下拉框中第一个值的方法
2018/02/06 Javascript
浅谈Vue数据响应思路之数组
2018/11/06 Javascript
jQuery插件实现非常实用的tab栏切换功能【案例】
2019/02/18 jQuery
vue简单封装axios插件和接口的统一管理操作示例
2020/02/02 Javascript
js实现mp3录音通过websocket实时传送+简易波形图效果
2020/06/12 Javascript
keep-alive保持组件状态的方法
2020/12/02 Javascript
[02:55]2018DOTA2国际邀请赛勇士令状不朽珍藏Ⅲ饰品一览
2018/08/01 DOTA
Python操作SQLite数据库的方法详解【导入,创建,游标,增删改查等】
2017/07/11 Python
Python基于ThreadingTCPServer创建多线程代理的方法示例
2018/01/11 Python
python中利用zfill方法自动给数字前面补0
2018/04/10 Python
python读取word 中指定位置的表格及表格数据
2019/10/23 Python
基于Python生成个性二维码过程详解
2020/03/05 Python
keras打印loss对权重的导数方式
2020/06/10 Python
浅谈如何使用python抓取网页中的动态数据实现
2020/08/17 Python
Python容器类型公共方法总结
2020/08/19 Python
详解python中的三种命令行模块(sys.argv,argparse,click)
2020/12/15 Python
全球地下的服装和态度:Slam Jam
2018/02/04 全球购物
英国游戏机和游戏购物网站:365games.co.uk
2018/06/18 全球购物
以实惠的价格轻松租车,免费取消:Easyrentcars
2019/07/16 全球购物
美丽的珠宝配饰:SmallThings
2019/09/04 全球购物
2015公务员年度考核评语
2015/03/25 职场文书
工程部岗位职责范本
2015/04/11 职场文书
新闻稿件写作范文
2015/07/18 职场文书
nginx proxy_cache 缓存配置详解
2021/03/31 Servers
python 实现的截屏工具
2021/05/08 Python
JavaScript与JQuery框架基础入门教程
2021/07/15 Javascript
openGauss数据库JDBC环境连接配置的详细过程(Eclipse)
2022/06/01 Java/Android