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 相关文章推荐
jquery获得下拉框值的代码
Aug 13 Javascript
javascript写的异步加载js文件函数(支持数组传参)
Jun 07 Javascript
javascript从作用域链谈闭包
Jul 29 Javascript
jQuery实现的兼容性浮动层示例
Aug 02 Javascript
AngularJS基础 ng-value 指令简单示例
Aug 03 Javascript
对Angular.js Controller如何进行单元测试
Oct 25 Javascript
解决AjaxFileupload 上传时会出现连接重置的问题
Jul 07 Javascript
jQuery实现用户信息表格的添加和删除功能
Sep 12 jQuery
微信小程序http连接访问解决方案的示例
Nov 05 Javascript
vue自定义键盘信息、监听数据变化的方法示例【基于vm.$watch】
Mar 16 Javascript
纯js+css实现仿移动端淘宝网站的弹出详情框功能
Dec 29 Javascript
jQuery实现动态操作table行
Nov 23 jQuery
小程序封装路由文件和路由方法(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
使用PHP计算两个路径的相对路径
2013/06/14 PHP
Yii2创建多界面主题(Theme)的方法
2016/10/08 PHP
IE和firefox浏览器的event事件兼容性汇总
2009/12/06 Javascript
jquery 多级下拉菜单核心代码
2010/05/21 Javascript
javascript 全等号运算符使用说明
2010/05/31 Javascript
jquery.cvtooltip.js 基于jquery的气泡提示插件
2010/11/19 Javascript
jQuery学习笔记之2个小技巧
2015/01/19 Javascript
JavaScript控制浏览器全屏及各种浏览器全屏模式的方法、属性和事件
2015/12/20 Javascript
Vue 实用分页paging实例代码
2017/04/12 Javascript
JS实现匀加速与匀减速运动的方法示例
2017/09/04 Javascript
详解浏览器缓存和webpack缓存配置
2018/07/06 Javascript
Vue中使用的EventBus有生命周期
2018/07/12 Javascript
ES11新增的这9个新特性,你都掌握了吗
2020/10/15 Javascript
[01:33:59]真人秀《加油 DOTA》 第六期
2014/09/09 DOTA
Python WXPY实现微信监控报警功能的代码
2017/10/20 Python
Python3多线程爬虫实例讲解代码
2018/01/05 Python
深入解析python中的实例方法、类方法和静态方法
2019/03/11 Python
Python识别快递条形码及Tesseract-OCR使用详解
2019/07/15 Python
Python 项目转化为so文件实例
2019/12/23 Python
动态设置django的model field的默认值操作步骤
2020/03/30 Python
基于Python测试程序是否有错误
2020/05/16 Python
python使用smtplib模块发送邮件
2020/12/17 Python
戴尔加拿大官网:Dell加拿大
2016/09/17 全球购物
英国马匹装备和马术用品购物网站:Equine Superstore
2019/03/03 全球购物
母亲80寿诞答谢词
2014/01/16 职场文书
研究生毕业鉴定
2014/01/29 职场文书
教师现实表现材料
2014/02/14 职场文书
初级党校心得体会
2014/09/11 职场文书
领导班子专题民主生活会情况想汇报
2014/09/30 职场文书
股权转让协议范本
2014/12/07 职场文书
家长意见书
2015/06/04 职场文书
Pytorch中的数据集划分&正则化方法
2021/05/27 Python
解决Vue+SpringBoot+Shiro跨域问题
2021/06/09 Vue.js
CSS+HTML 实现顶部导航栏功能
2021/08/30 HTML / CSS
MySQL 用 limit 为什么会影响性能
2021/09/15 MySQL
php实例化对象的实例方法
2021/11/17 PHP