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(二)--- Node.js事件模块
May 21 NodeJs
nodejs中使用monk访问mongodb
Jul 06 NodeJs
nodejs批量修改文件编码格式
Jan 22 NodeJs
nodejs通过phantomjs实现下载网页
May 04 NodeJs
详解nodejs express下使用redis管理session
Apr 24 NodeJs
Nodejs实现多房间简易聊天室功能
Jun 20 NodeJs
深入学习nodejs中的async模块的使用方法
Jul 12 NodeJs
修改Nodejs内置的npm默认配置路径方法
May 13 NodeJs
nodejs前端模板引擎swig入门详解
May 15 NodeJs
Nodejs中的JWT和Session的使用
Aug 21 NodeJs
nodejs中实现用户注册路由功能
May 20 NodeJs
nodejs简单抓包工具使用详解
Aug 23 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输入流php://input使用示例(php发送图片流到服务器)
2013/12/25 PHP
php向js函数传参的几种方法
2014/08/10 PHP
PHP Warning: Module 'modulename' already loaded in问题解决办法
2015/03/16 PHP
分享10段PHP常用代码
2015/11/11 PHP
[原创]解决wincache不支持64位PHP5.5/5.6的问题(提供64位wincache下载)
2016/06/22 PHP
使用正则去除php代码中的注释方法
2016/11/03 PHP
IE关闭时判断及AJAX注销案例学习
2013/02/18 Javascript
JS操作Cookie写入和读取实例代码
2013/10/20 Javascript
React实现双向绑定示例代码
2016/09/19 Javascript
利用Jquery实现几款漂亮实用的时间轴(附示例代码)
2017/02/15 Javascript
js面向对象编程总结
2017/02/16 Javascript
vue一步步实现alert功能
2017/07/05 Javascript
node.js 发布订阅模式的实例
2017/09/10 Javascript
原生JS实现网页手机音乐播放器 歌词同步播放的示例
2018/02/02 Javascript
微信小程序swiper实现滑动放大缩小效果
2018/11/15 Javascript
[02:40]DOTA2殁境神蚀者 英雄基础教程
2013/11/26 DOTA
[37:37]DAC2018 4.4 淘汰赛 Optic vs Mineski 第二场
2018/04/05 DOTA
Python栈类实例分析
2015/06/15 Python
python制作websocket服务器实例分享
2016/11/20 Python
Python Socket编程详细介绍
2017/03/23 Python
python 连接sqlite及简单操作
2017/06/30 Python
PyCharm设置SSH远程调试的方法
2018/07/17 Python
python 使用socket传输图片视频等文件的实现方式
2019/08/07 Python
Python常用模块sys,os,time,random功能与用法实例分析
2020/01/07 Python
简单了解pytest测试框架setup和tearDown
2020/04/14 Python
Python面向对象特殊属性及方法解析
2020/09/16 Python
Timex手表官网:美国运动休闲手表品牌
2017/01/28 全球购物
全球性的在线购物网站:Zapals
2017/03/22 全球购物
美国孕妇装购物网站:Motherhood Maternity
2019/09/22 全球购物
char型变量中能不能存贮一个中文汉字
2015/07/08 面试题
深圳-东方伟业笔试部分
2015/02/11 面试题
《盘古开天地》教学反思
2014/02/28 职场文书
小学语文国培感言
2014/03/04 职场文书
党的群众路线教育实践活动动员会主持词
2014/03/20 职场文书
2014年控辍保学工作总结
2014/12/08 职场文书
golang switch语句的灵活写法介绍
2021/05/06 Golang