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 25 NodeJs
Nodejs爬虫进阶教程之异步并发控制
Feb 15 NodeJs
nodejs中全局变量的实例解析
Mar 07 NodeJs
详解nodejs微信公众号开发——3.封装消息响应模块
Apr 10 NodeJs
用Nodejs搭建服务器访问html、css、JS等静态资源文件
Apr 28 NodeJs
详解使用nodeJs安装Vue-cli
May 17 NodeJs
nodejs集成sqlite使用示例
Jun 05 NodeJs
NodeJS实现不可逆加密与密码密文保存的方法
Mar 16 NodeJs
nodejs实现超简单生成二维码的方法
Mar 17 NodeJs
深入理解NodeJS 多进程和集群
Oct 17 NodeJs
浅谈JS和Nodejs中的事件驱动
May 05 NodeJs
NodeJs使用webpack打包项目的方法详解
Feb 28 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者的疑难问答(1)
2006/10/09 PHP
手把手教你使用DedeCms的采集的图文教程
2007/03/11 PHP
站长助手-网站web在线管理程序 v1.0 下载
2007/05/12 PHP
php array_search() 函数使用
2010/04/13 PHP
那些年一起学习的PHP(二)
2012/03/21 PHP
Apache下禁止php文件被直接访问的解决方案
2013/04/25 PHP
php单文件版在线代码编辑器
2015/03/12 PHP
php基于dom实现读取图书xml格式数据的方法
2017/02/03 PHP
Thinkphp 空操作、空控制器、命名空间(详解)
2017/05/05 PHP
利用PHPStorm如何开发Laravel应用详解
2017/08/30 PHP
PHP集成环境XAMPP的安装与配置
2018/11/13 PHP
js 居中漂浮广告
2010/03/21 Javascript
js页面跳转常用的几种方式
2010/11/25 Javascript
JavaScript中数组对象的那些自带方法介绍
2013/03/12 Javascript
js 中的switch表达式使用示例
2020/06/03 Javascript
Jquery遍历节点的方法小集
2014/01/22 Javascript
nodejs分页类代码分享
2014/06/17 NodeJs
微信分享的标题、缩略图、连接及描述设置方法
2014/10/14 Javascript
基于jquery的手风琴图片展示效果实现方法
2014/12/16 Javascript
微信小程序实现下拉菜单切换效果
2020/03/30 Javascript
小程序Request的另类用法详解
2019/08/09 Javascript
Vue+Node实现商品列表的分页、排序、筛选,添加购物车功能详解
2019/12/07 Javascript
JavaScript 类的封装操作示例详解
2020/05/16 Javascript
[38:41]2014 DOTA2国际邀请赛中国区预选赛 LGD VS CNB
2014/05/22 DOTA
简单介绍Python的Django框架加载模版的方式
2015/07/20 Python
Python使用Pandas对csv文件进行数据处理的方法
2019/08/01 Python
python实现H2O中的随机森林算法介绍及其项目实战
2019/08/29 Python
python函数装饰器之带参数的函数和带参数的装饰器用法示例
2019/11/06 Python
Python字典中的值为列表或字典的构造实例
2019/12/16 Python
美国战术品牌:5.11 Tactical
2019/05/01 全球购物
Ariat官网:美国马靴和服装品牌
2019/12/16 全球购物
.NET初级开发工程师面试题(包括Javascript)
2012/08/22 面试题
和平主题的演讲稿
2014/01/12 职场文书
土地转让协议书范本
2014/04/15 职场文书
群众路线教育实践活动实施方案
2014/10/31 职场文书
农业项目合作意向书
2015/05/08 职场文书