nodejs中使用多线程编程的方法实例


Posted in NodeJs onMarch 24, 2015

在以前的博文别说不可能,nodejs中实现sleep中,我向大家介绍了nodejs addon的用法。今天的主题还是addon,继续挖掘c/c++的能力,弥补nodejs的弱点。

我曾多次提到过nodejs的性能问题。其实就语言本身而言,nodejs的性能还是很高的,虽然不及大多部静态语言,但差距也并不大;相对其他动态语言而言,速度优势非常明显。但为什么我们常常说nodejs不能胜任CPU密集型场景呢?因为由于其单线程特性,对于CPU密集型场景,它并不能充分利用CPU。计算机科学中有一个著名的Amdahl定律:

nodejs中使用多线程编程的方法实例

假设总工作量W,可以分解为两个部分:只能串行计算的Ws和允许并行计算的Wp。那么,在p个CPU并行计算的情况下,性能上能够带来speedup倍的提升。Amdahl定律描述了并行能做到的和不能做到的。它是一种理想情况,实际情况会复杂得多。比如并发很可能会引起资源的争夺,需要增加各种锁,从而常常让并行处于等待状态;并发还会额外带来操作系统对线程调度切换的时间开销,增加Ws。不过,当一项任务中,Wp比Ws大得多,并且有多个CPU核心可供使用时,并行带来的性能提升是相当可观的。

好,回到nodejs上。我们设想一个计算场景:计算4000000内的质数数目。这个场景编程实现的时候,以除法运算为主,不涉及内存、对象等操作,理论上能够确保让nodejs以相对较快的速度运行,不会落后c太多,便于对比。

javascript寻找质数的方法已经在这篇博客中提供了,直接抄过来:

function zhishu_js(num) {

    if (num == 1) {

        return false;

    }

    if (num == 2) {

        return true;

    }

    for (var i = 2; i <= Math.sqrt(num); i++) {

        if (num % i == 0) {

            return false;

        }

    }

    return true;

}

再写一个c语言版本的:

#include <math.h>
bool zhishu(int num){

    if (num == 1) {

        return false;

    }

    if (num == 2) {

        return true;

    }

    for (int i = 2; i <= sqrt(num); i++) {

        if (num % i == 0) {

            return false;

        }

    }

    return true;

};

在nodejs中,我们用一个从1到4000000的循环来检索质数;c语言中,我们设置若干个线程,定义count为4000000,每个线程做如下操作要:如果count大于0,则取出count的值,并计算是否为质数,同时将count减1。根据这个思路,javascript版本的很容易写:

var count = 0;
for (j = 1; j < 4000000; j++) {

    if(zhishu(j)){

        count++;

    }

}

关键难点就是c语言的多线程编程。早期c/c++并没有考虑并行计算的需求,所以标准库中并没有提供多线程支持。而不同的操作系统通常实现也是有区别的。为了避免这种麻烦,我们采用pthread来处理线程。

下载pthread最新版本。由于我对gyp不熟,link依赖lib搞了半天没搞定,最后我的方式是,直接把pthread的源代码放到了项目目录下,并在binding.gyp中把pthread.c添加到源代码列表中,在编译项目的时候把pthread也编译一次。修改后的binding.gyp是这样的:

{

  "targets": [

    {

      "target_name": "hello",

      "sources": [ "hello.cc","pthreads/pthread.c" ],

      "include_dirs": [

        "<!(node -e \"require('nan')\")",

        "pthreads"

      ],

      "libraries": ["Ws2_32.lib"]

    }

  ]

}

 当然了,我这种方法很麻烦,如果你们只添加pthread中lib和include目录的引用,并且不出现依赖问题,那是最好的,就没有必要用我的方法来做。

那么接下来就进入C/C++多线程的一切了,定义一个线程处理函数:

pthread_mutex_t lock;
void *thread_p(void *null){

    int num, x=0;

    do{

        pthread_mutex_lock(&lock);

        num=count--;

        pthread_mutex_unlock(&lock);

        if(num>0){

            if(zhishu(num))x++;

        }else{

            break;

        }

    }while(true);

    std::cout<<' '<<x<<' ';

    pthread_exit(NULL);

        return null;

}

 在线程与线程之间,对于count这个变量是相互竞争的,我们需要确保同时只能有一个线程操作count变量。我们通过 pthread_mutex_t lock; 添加一个互斥锁。当执行 pthread_mutex_lock(&lock); 时,线程检查lock锁的情况,如果已锁定,则等待、重复检查,阻塞后续代码运行;如果锁已释放,则锁定,并执行后续代码。相应的, pthread_mutex_unlock(&lock); 就是解除锁状态。

由于编译器在编译的同时,进行编译优化,如果一个语句没有明确做什么事情,对其他语句的执行也没有影响时,会被编译器优化掉。在上面的代码中,我加入了统计质数数量的代码,如果不加的话,像这样的代码:

for (int j = 0; j < 4000000; j++) {

    zhishu(j);

}

 是会直接被编译器跳过的,实际不会运行。

添加addon的写法已经介绍过了,我们实现从javascript接收一个参数,表示线程数,然后在c中创建指定数量的线程完成质数检索。完整代码:

#include <nan.h>

#include <math.h>

#include <iostream>

#include "pthreads\pthread.h"

#define MAX_THREAD 100

using namespace v8;
int count=4000000;

pthread_t tid[MAX_THREAD];

pthread_mutex_t lock;
void *thread_p(void *null){

    int num, x=0;

    do{

        pthread_mutex_lock(&lock);

        num=count--;

        pthread_mutex_unlock(&lock);

        if(num>0){

            if(zhishu(num))x++;

        }else{

            break;

        }

    }while(true);

    std::cout<<' '<<x<<' ';

    pthread_exit(NULL);

    return null;

}
NAN_METHOD(Zhishu){

    NanScope();

    pthread_mutex_init(&lock,NULL);

    double arg0=args[0]->NumberValue();

    int c=0;

    for (int j = 0; j < arg0 && j<MAX_THREAD; j++) {

        pthread_create(&tid[j],NULL,thread_p,NULL);

    }

    for (int j = 0; j < arg0 && j<MAX_THREAD; j++) {

        pthread_join(tid[j],NULL);

    }

    NanReturnUndefined();

}
void Init(Handle<Object> exports){

    exports->Set(NanSymbol("zhishu"), FunctionTemplate::New(Zhishu)->GetFunction());

}
NODE_MODULE(hello, Init);

 phread_create可以创建线程,默认是joinable的,这个时候子线程受制于主线程;phread_join阻塞住主线程,等待子线程join,直到子线程退出。如果子线程已退出,则phread_join不会做任何事。所以对所有的线程都执行thread_join,可以保证所有的线程退出后才会例主线程继续进行。

完善一下nodejs脚本:

var zhishu_c=require('./build/Release/hello.node').zhishu;

function zhishu(num) {

    if (num == 1) {

        return false;

    }

    if (num == 2) {

        return true;

    }

    for (var i = 2; i <= Math.sqrt(num); i++) {

        if (num % i == 0) {

            return false;

        }

    }

    return true;

}
console.time("c");

    zhishu_c(100);

console.timeEnd("c");
console.time("js");

var count=0;

for (j = 1; j < 4000000; j++) {

    if(zhishu(j)){

        count++;

    }

}

console.log(count);

console.timeEnd("js");

 看一下测试结果:

nodejs中使用多线程编程的方法实例

 单线程时,虽然C/C++的运行速度是nodejs的181%,但这个成绩我们认为在动态语言中,还是非常不错的。双线程时速度提升最明显,那是因为我的电脑是双核四线程CPU,这个时候已经可能在使用两个核心在进行处理。4线程时速度达到最大,此时应该是双核四线程能达到的极限,当线程再增加时,并不能再提升速度了。上述Amdahl定律中,p已达上限4。再增加线程,会增加操作系统进程调度的时间,增加锁的时间,尽管同时也能增加对CPU时间的竞争,但总体而言,Ws的增加更加明显,性能是下降的。如果在一台空闲的机器上做这个实验,数据应该会更好一点。

从这个实验中,我们可以得出这样的结论,对于CPU密集型的运算,交给静态语言去做,效率会提高很多,如果计算中较多涉及内存、字符串、数组、递归等操作(以后再验证),性能提升更为惊人。同时,合理地利用多线程能有效地提高处理效率,但并不是线程越多越好,要根据机器的情况合理配置。

对于nodejs本身,的确是不擅长处理CPU密集的任务,但有了本文的经验,我想,想克服这个障碍,并非什么不可能的事情。

NodeJs 相关文章推荐
Nodejs进程管理模块forever详解
Jun 01 NodeJs
使用forever管理nodejs应用教程
Jun 03 NodeJs
基于NodeJS的前后端分离的思考与实践(三)轻量级的接口配置建模框架
Sep 26 NodeJs
nodejs中使用多线程编程的方法实例
Mar 24 NodeJs
Nodejs如何复制文件
Mar 09 NodeJs
详解nodejs中exports和module.exports的区别
Feb 17 NodeJs
使用 NodeJS+Express 开发服务端的简单介绍
Apr 07 NodeJs
nodejs处理图片的中间件node-images详解
May 08 NodeJs
nodejs实现的简单web服务器功能示例
Mar 15 NodeJs
nodejs连接mysql数据库及基本知识点详解
Mar 20 NodeJs
NodeJS实现同步的方法
Mar 02 NodeJs
浅谈vue websocket nodeJS 进行实时通信踩到的坑
Sep 22 NodeJs
nodejs中实现sleep功能实例
Mar 24 #NodeJs
nodejs中的fiber(纤程)库详解
Mar 24 #NodeJs
nodeJS代码实现计算交社保是否合适
Mar 09 #NodeJs
Nodejs关于gzip/deflate压缩详解
Mar 04 #NodeJs
nodejs URL模块操作URL相关方法介绍
Mar 03 #NodeJs
Windows系统中安装nodejs图文教程
Feb 28 #NodeJs
NodeJS中利用Promise来封装异步函数
Feb 25 #NodeJs
You might like
PHP has encountered an Access Violation at 7C94BD02解决方法
2009/08/24 PHP
php日期转时间戳,指定日期转换成时间戳
2012/07/17 PHP
PHP 的异常处理、错误的抛出及回调函数等面向对象的错误处理方法
2012/12/07 PHP
Laravel中使用阿里云OSS Composer包分享
2015/02/10 PHP
自制PHP框架之设计模式
2017/05/07 PHP
PHP面向对象程序设计之构造方法和析构方法详解
2019/06/13 PHP
基于jquery的一个图片hover的插件
2010/04/24 Javascript
JS 事件绑定函数代码
2010/04/28 Javascript
JQuery之拖拽插件实现代码
2011/04/14 Javascript
jquery ajax请求实例深入解析
2012/11/26 Javascript
实测jquery data()如何存值
2013/08/18 Javascript
js实现目录定位正文示例
2013/11/14 Javascript
javascript背景时钟实现方法
2015/06/18 Javascript
Highcharts入门之基本属性
2016/08/02 Javascript
微信小程序去哪里找 小程序到底如何使用(附小程序名单)
2017/01/09 Javascript
javascript基本常用排序算法解析
2017/09/27 Javascript
fetch 使用及如何接收JS传值
2017/11/11 Javascript
Vue 过滤器filters及基本用法
2017/12/26 Javascript
Node.js使用supervisor进行开发中调试的方法
2019/03/26 Javascript
JavaScript生成随机验证码代码实例
2019/09/28 Javascript
微信小程序实现横向滚动导航栏效果
2019/12/12 Javascript
[59:00]OG vs TNC 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
基python实现多线程网页爬虫
2015/09/06 Python
利用Python操作消息队列RabbitMQ的方法教程
2017/07/19 Python
解决pandas .to_excel不覆盖已有sheet的问题
2018/12/10 Python
Python eval的常见错误封装及利用原理详解
2019/03/26 Python
PyQt5 界面显示无响应的实现
2020/03/26 Python
一张图片能隐含千言万语之隐藏你的程序代码
2012/12/13 HTML / CSS
使用spring mvc+localResizeIMG实现HTML5端图片压缩上传的功能
2016/12/16 HTML / CSS
Html5在手机端调用相机的方法实现
2020/05/13 HTML / CSS
自荐书格式
2013/12/01 职场文书
巾帼建功标兵事迹材料
2014/05/11 职场文书
试用期转正工作总结2015
2015/05/28 职场文书
给原生html中添加水印遮罩层的实现示例
2021/04/02 Javascript
python实现图片批量压缩
2021/04/24 Python
pytorch实现ResNet结构的实例代码
2021/05/17 Python