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开发微信公众号后台服务实例
Sep 03 NodeJs
轻松创建nodejs服务器(5):事件处理程序
Dec 18 NodeJs
轻松创建nodejs服务器(10):处理上传图片
Dec 18 NodeJs
Nodejs初级阶段之express
Nov 23 NodeJs
详解nodejs与javascript中的aes加密
May 22 NodeJs
NodeJS远程代码执行
Aug 28 NodeJs
Jquery通过ajax请求NodeJS返回json数据实例
Nov 08 NodeJs
nodejs利用http模块实现银行卡所属银行查询和骚扰电话验证示例
Dec 30 NodeJs
基于Nodejs利用socket.io实现多人聊天室
Feb 22 NodeJs
nodejs构建本地web测试服务器 如何解决访问静态资源问题
Jul 14 NodeJs
nodejs基础之常用工具模块util用法分析
Dec 26 NodeJs
nodejs中使用worker_threads来创建新的线程的方法
Jan 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初学者常见问题集合 修正版(21问答)
2010/03/23 PHP
Smarty局部缓存的几种方法简介
2014/06/17 PHP
PHP实现通过Luhn算法校验信用卡卡号是否有效
2015/03/23 PHP
PHP面向对象程序设计之对象生成方法详解
2016/12/02 PHP
PHP实现使用DOM将XML数据存入数组的方法示例
2017/09/27 PHP
PHP常见的序列化与反序列化操作实例分析
2019/10/28 PHP
兼容多浏览器的字幕特效Marquee的通用js类
2008/07/20 Javascript
javascript 设置文本框中焦点的位置
2009/11/20 Javascript
js 编写规范
2010/03/03 Javascript
JavaScript获取当前日期是星期几的方法
2015/04/06 Javascript
JS简单实现点击复制链接的方法
2016/08/03 Javascript
关于iframe跨域POST提交的方法示例
2017/01/15 Javascript
基于bootstrop常用类总结(推荐)
2017/09/11 Javascript
Nuxt.js实现校验访问浏览器类型的中间件
2018/08/24 Javascript
vuejs router history 配置到iis的方法
2018/09/20 Javascript
jQuery实现王者荣耀手风琴效果
2020/01/17 jQuery
nuxt 实现在其它js文件中使用store的方式
2020/11/05 Javascript
Python爬虫爬验证码实现功能详解
2016/04/14 Python
Python Nose框架编写测试用例方法
2017/10/26 Python
django开发教程之利用缓存文件进行页面缓存的方法
2017/11/10 Python
解决Python获取字典dict中不存在的值时出错问题
2018/10/17 Python
在Pycharm中修改文件默认打开方式的方法
2019/01/17 Python
windows下python安装pip方法详解
2020/02/10 Python
python+selenium 脚本实现每天自动登记的思路详解
2020/03/11 Python
使用豆瓣源来安装python中的第三方库方法
2021/01/26 Python
微信html5页面调用第三方位置导航的示例
2018/03/14 HTML / CSS
澳大利亚最大的护发和护肤品购物网站:RY
2019/12/26 全球购物
.NET现在共支持多少种语言
2014/02/26 面试题
平面设计专业大学生职业规划书
2014/03/12 职场文书
社区党建工作方案
2014/06/10 职场文书
酒店餐厅2014重阳节活动策划方案
2014/09/16 职场文书
2014年街道办事处工作总结
2014/12/11 职场文书
python实现简单的井字棋
2021/05/26 Python
nginx安装以及配置的详细过程记录
2021/09/15 Servers
Nginx图片服务器配置之后图片访问404的问题解决
2022/03/21 Servers
Redis基本数据类型Zset有序集合常用操作
2022/06/01 Redis