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

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

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

假设总工作量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");

看一下测试结果:

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

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

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

(0)

相关推荐

  • js基于setTimeout与setInterval实现多线程

    本文实例讲述了js基于setTimeout与setInterval实现多线程的方法.分享给大家供大家参考,具体如下: javascript无法实现线程阻塞(sleep),原因是因为sleep涉及系统调用.js出于安全考虑是不允许系统调用的. 如果一定要实现语句继续执行就只能用while(1)空转的方法消耗CPU,判断到了时间就break.不过这个方法也不是真正sleep. 只执行一次的定时器 <script> //定时器使用的是异步的方式运行的 function hello(){ alert(

  • JavaScript是否可实现多线程 深入理解JavaScript定时机制

    容易欺骗别人感情的JavaScript定时器 JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如 复制代码 代码如下: setTimeout( function(){ alert('你好!'); } , 0); setInterval( callbackFunction , 100); 认为setTimeout中的问候方法会立即被执行,因为这并不是凭空而说,而是JavaS

  • Js setInterval与setTimeout(定时执行与循环执行)的代码(可以传入参数)

    Document自带的方法: 循环执行:var timeid = window.setInterval("方法名或方法","延时");window.clearInterval(timeid); 定时执行:var tmid = window.setTimeout("方法名或方法", "延时");window.clearTimeout(tmid); 举例说明: A.当要执行的方法中不需要参数时 复制代码 代码如下: <scr

  • JavaScript多线程的实现方法

    注:以下内容基于IE中GIF的onload事件的基础上,故所有测试IE only 需要用到的几个图片 先看一个简单的事实: 复制代码 代码如下: <SCRIPT LANGUAGE="JavaScript"> var img=new Image(); img.src="attachment/1178365293_0.gif"; img.onload=function() { alert("如要关闭请按住ESC键不放,并点击关闭按钮");

  • JavaScript使用yield模拟多线程的方法

    本文实例讲述了JavaScript使用yield模拟多线程的方法.分享给大家供大家参考.具体分析如下: 在python和C#中都有yield方法,通过yield可以实现很多多线程才能实现的功能. 对javascript有版本要求:JavaScript 1.7 function Thread( name ) { for ( var i = 0; i < 5; i++ ) { Print(name+': '+i); yield; } } //// thread management var thre

  • JavaScript SetInterval与setTimeout使用方法详解

    setTimeout和setInterval的语法相同.它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码.不过这两个函数还是有区别的,setInterval在执行完一次代码之后,经过了那个固定的时间间隔,它还会自动重复执行代码,而setTimeout只执行一次那段代码.区别:window.setTimeout("function",time)://设置一个超时对象,只执行一次,无周期 window.setInterva

  • JS模拟多线程

    var Thread = { runNum : 0,  //当前正式运行的线程数 maxNum : 10, //最大同时执行的线程数 -1表示不限 commandList : new Array(),  start : function(){  //window.status = this.runNum;   if(this.maxNum != -1 && this.runNum >= this.maxNum){      return;   }  if(this.commandLi

  • JavaScript中的Web worker多线程API研究

    HTML5支持了Web Worker这样的API,允许网页在安全的情况下执行多线程代码.不过Web Worker实际上受到很多限制,因为它无法真正意义上共享内存数据,只能通过消息来做状态通知,所以甚至不能称之为真正意义上的"多线程". Web Worker的接口使用起来很不方便,它基本上自带一个sandbox,在沙箱中跑一个独立的js文件,通过 postMessage和 onMessge来和主线程通信: 复制代码 代码如下: var worker = new Worker("

  • JavaScript多线程详解

    虽然有越来越多的网站在应用AJAX技术进行开发,但是构建一个复杂的AJAX应用仍然是一个难题. 造成这些困难的主要原因是什么呢?是与服务器的异步通信问题?还是GUI程序设计问题呢?通常这两项工作都是由桌面程序来完成的,那究竟为何开发一个可以实现同样功能的AJAX应用就这么困难呢? 大家都知道javascript是单线程执行的,但是又可以通过setTimeout或者setInterval定时执行一个方法,通过Ajax可以在向服务器端发送请求没有收到回应可以继续执行主逻辑.这些是如何做到的呢,下面就

  • 小试JavaScript多线程第1/2页

    法宝就是Concurrent.Thread这个家伙,其实是一个js库,你可以从网站下载源代码.如何使用呢? 很简单 Concurrent.Thread.create(f, a1, a2, ...) f为你要调用的函数,a1,a2为该函数的参数,这样创建了一个线程,你可以同时创建多个,他们会同时去执行,这个库同时提供 了很多的方法,类似其它语言里的Thread使用方法,如Concurrent.Thread.stop();等.具体去网站去了解. 复制代码 代码如下: <html> <body

  • JavaScript可否多线程? 深入理解JavaScript定时机制

    例如 复制代码 代码如下: setTimeout( function(){ alert('你好!'); } , 0); setInterval( callbackFunction , 100); 认为setTimeout中的问候方法会立即被执行,因为这并不是凭空而说,而是JavaScript API文档明确定义第二个参数意义为隔多少毫秒后,回调方法就会被执行. 这里设成0毫秒,理所当然就立即被执行了. 同理对setInterval的callbackFunction方法每间隔100毫秒就立即被执行

  • Js中setTimeout()和setInterval() 何时被调用执行的用法

    定义setTimeout()和setInterval()经常被用来处理延时和定时任务.setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,而setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除.从定义上我们可以看到两个函数十分类似,只不过前者执行一次,而后者可以执行多次,两个函数的参数也相同,第一个参数是要执行的code或句柄,第二个是延迟的毫秒数.很简单的定义,使用起来也很简单,但有时候我们的代码并不是按照我们的想象

随机推荐