Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 415|回复: 0

Nodejs密集型CPU解决方案

[复制链接]
  • TA的每日心情
    奋斗
    3 小时前
  • 签到天数: 751 天

    [LV.10]以坛为家III

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    706360
    发表于 2021-4-21 11:37:49 | 显示全部楼层 |阅读模式

    首先说一下nodejs单线程的优势:

    高性能,与php相比,避免了频繁创建切换线程的开销,执行更加迅速,资源占用小。

    线程安全,不用担心同一变量被多线程读写,造成程序崩溃。

    单线程的异步和非阻塞,其实 nodejs底层访问I/O还是多线程的,阻塞/非阻塞与异步/同步是两个不同的概念,同步不代表阻塞,但是阻塞肯定就是同步;有点儿绕口,请听我举例,我去食堂打饭,我选择了A套餐,然后工作人员帮我去配餐,如果我就站在旁边,等待工作人员给我配餐,这种情况就称之为同步;若工作人员帮我配餐的同时,排在我后面的人就开始点餐,这样整个食堂的点餐服务并没有因为我在等待A套餐而停止,这种情况就称之为非阻塞。这个例子就简单说明了同步但非阻塞的情况。再如果我在等待配餐的时候去买饮料,等听到叫号再回去拿套餐,此时我的饮料也已经买好,这样我在等待配餐的同时还执行了买饮料的任务,叫号就等于执行了回调,就是异步非阻塞了。如果我在买饮料的时候,已经叫我的号让我去拿套餐,可是我等了好久才拿到饮料,所以我可能在大厅叫我的餐号之后很久才拿到A套餐,这也就是单线程的阻塞情况。

    多线程:

    线程是cpu调度的一个基本单位,一个cpu只能执行一个线程任务。

    nodejs也可以执行多行程任务,例如引用TAGG/TAGG2模块,大家可以看一下我上一篇文章,里面有具体的使用方法。但是不论是tagg/tagg2都是利用pthread库和V8::Isolate类来实现js多线程功能的,根据规则我们在线程里执行的函数无法使用nodejs的核心api,例如fs,crypto模块,所以还是有很大的局限性。

    多进程:

    在支持html5的浏览器里,我们可以使用webworker来将一些耗时的计算丢入worker进程中执行,这样主进程就不会阻塞,用户也不会有卡顿的感觉。

    这里我们需要利用nodejs的child_process模块,child_process提供了fork方法,可以启动一个nodejs文件,将它视作worker进程,worker 工作完毕后,会把结果send给主进程,然后worker自动退出,这样我们就利用了多进程解决了主线程阻塞的问题。

    var express = require('express');
    var fork = require('child_process').fork;
    var app = express();
    app.get('/', function(req, res){
      var worker = fork('./work.js') //创建一个工作进程
      worker.on('message', function(m) {//接收工作进程计算结果
              if('object' === typeof m && m.type === 'fibo'){
                       worker.kill();//发送杀死进程的信号
                       res.send(m.result.toString());//将结果返回客户端
              }
      });
      worker.send({type:'fibo',num:~~req.query.n || 1});
      //发送给工作进程计算fibo的数量
    });
    app.listen(7878);
    

    我们通过express监听7878端口,对每个用户的请求都会去fork一个子进程,通过调用worker.send方法将参数n传递给子进程,同时监听子进程发送消息的message事件,将结果响应给客户端。

    下面是被fork的work.js文件内容:

    var fibo = function fibo (n) {//定义算法
       return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
    }
    process.on('message', function(m) {
    //接收主进程发送过来的消息
              if(typeof m === 'object' && m.type === 'fibo'){
                      var num = fibo(~~m.num);
                      //计算jibo
                      process.send({type: 'fibo',result:num})
                      //计算完毕返回结果        
              }
    });
    process.on('SIGHUP', function() {
            process.exit();//收到kill信息,进程退出
    });
    

    我们先定义函数fibo用来计算斐波那契数组,然后监听了主线程发来的消息,计算完毕之后将结果send到主线程。同时还监听process的SIGHUP事件,触发此事件就进程退出。

    这里我们有一点需要注意,主线程的kill方法并不是真的使子进程退出,而是会触发子进程的SIGHUP事件,真正的退出还是依靠process.exit()。

    总结:

    使用child_process模块的fork方法确实可以让我们很好的解决单线程对cpu密集型任务的阻塞问题,同时又没有tagg包那样无法使用Node.js核心api的限制。

    单线程异步的Node.js不代表不会阻塞,在主线程做过多的任务可能会导致主线程的卡死,影响整个程序的性能,所以我们要非常小心的处理大量的循环,字符串拼接和浮点运算等cpu密集型任务,合理的利用各种技术把任务丢给子线程或子进程去完成,保持Node.js主线程的畅通。

    线程/进程的使用并不是没有开销的,尽可能减少创建和销毁线程/进程的次数,可以提升我们系统整体的性能和出错的概率。

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-6-20 23:11 , Processed in 0.057889 second(s), 27 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表