Node.js异步处理CPU密集型任务

Node.js擅长数据密集型实时(data-intensive real-time)交互的应用场景。然而数据密集型实时应用程序并非仅仅有I/O密集型任务,当碰到CPU密集型任务时,比方要对数据加解密(node.bcrypt.js),数据压缩和解压(node-tar),或者要依据用户的身份对图片做些个性化处理,在这些场景下,主线程致力于做复杂的CPU计算,IO请求队列中的任务就被堵塞。

Node.js主线程的event loop在处理全部的任务/事件时,都是沿着事件队列顺序执行的,所以在当中不论什么一个任务/事件本身没有完毕之前,其他的回调、监听器、超时、nextTick()的函数都得不到执行的机会,由于被堵塞的event loop根本没机会处理它们,此时程序最好的情况是变慢,最糟的情况是停滞不动,像死掉一样。

一个可行的解决方式是新开进程,通过ipc通信,将CPU密集型任务交给子进程,子进程计算完成后,再通过ipc消息通知主进程,并将结果返回给主进程[1]。

和创建线程相比,开辟新进程的系统资源占用率大,进程间通信效率也不高。假设能不开新进程而是新开线程,将CPU耗时任务交给一个工作线程去做,然后主线程马上返回,处理其它的IO请求,等到工作线程计算完成后,通知主线程并将结果返回给主线程。那么在同一时候面对IO密集型和CPU密集型服务的场景下,Node.js的主线程也会变得轻松,并能时刻保持高对应度。

因此,和开进程相比,一个更加优秀的解决方式是:

不开进程,而是将CPU耗时操作交给进程内的一个工作线程完毕。

CPU耗时操作的详细逻辑支持通过C++和Js实现。

3 js使用这个机制与使用IO库类似,方便高效。

4 在新线程中执行一个独立的V8 VM,与主线程的VM并发执行,而且这个线程必须  由我们自己托管。

为了实现以上四个目标,我们在Node中添加�了一个backgroundthread线程,文章稍候会详解这个概念。在详细实现上,为Node添加�了一个’pt_c’的内建C++模块。这个模块负责吧CPU耗时操作封装成一个Task,抛给backgroundthread,然后马上返回。详细的逻辑在还有一个线程中处理,完毕之后,设定结果,通知主线程。这个过程很类似于异步IO请求。详细逻辑例如以下图:

BackgroundThread

Node提供了一种机制能够将CPU耗时操作交给其它线程去做,等到运行完成后设置结果通知主线程运行callback函数。下面是一段代码,用来演示这个过程:

int main() {

loop = uv_default_loop();

int data[FIB_UNTIL];

uv_work_t req[FIB_UNTIL];

int i;

; i < FIB_UNTIL; i++) {

data[i] = i;

req[i].data = (void *) &data[i];

uv_queue_work(loop, &req[i], fib, after_fib);

}

return uv_run(loop, UV_RUN_DEFAULT);

}

当中函数uv_queue_work的定义例如以下:

UV_EXTERN int uv_queue_work(uv_loop_t* loop,

uv_work_t* req,

uv_work_cb work_cb,

uv_after_work_cb after_work_cb);

參数 work_cb 是在另外线程运行的函数指针,after_work_cb相当于给主线程运行的回调函数。

在windows平台上,uv_queue_work终于调用API函数QueueUserWorkItem来派发这个task,终于运行task 的线程是由操作系统托管的,每次可能都不一样。这不满足上述第四条。

由于我们要支持在线程中执行js代码,这就须要开一个V8 VM,所以须要把这个线程固定下来,特定任务,仅仅交给这个线程处理。而且一旦创建,无论有没有task,都不能随便退出。这就须要我们自己维护一个线程对象,而且提供接口,使得使用者能够方便的生成一个对象而且提交给这个线程的任务队列。

在node进程启动初始化过程中,添�一个创建background thread对象的过程。这个线程拥有一个taskloop,有任务就处理,没有任务就等待在一个信号量上。多线程要考虑线程间同步的问题。线程同步仅仅发生在读写此线程的incomming queue 的时候。Node的主线程生成task后,提交到这个线程的incomming queue中,并激活信号量然后马上返回。在下一次循环中,backgroundthread从incomming queue中取出全部的task,放入working queue,然后依次运行working queue中的task。主线程不訪问working queue因此不须要加锁。这样做能够减少冲突。

这个线程在进入taskloop循环之前会建立一个独立的v8 VM,专门用来运行backgroundjs的代码。主线程的v8引擎和这个线程的能够并行运行。它的生命周期与Node进程的生命周期一致。

BackgroundJs

能够把全部CPU耗时逻辑放入backgroundJs中,主线程通过生成一个task,指定好执行的函数和參数,抛给工作线程。工作线程在执行task的过程中调用在backgroundJs中的函数。BackgroundJs是一个.js文件,在里面加入�CPU耗时函数。

background.js代码演示样例:

var globalFunction = function(v){

var flag;

try

{

flag = true;

JSON.parse(v);

}

catch(e)

{

flag = false;

}

if(!flag)

{

var err = 'err';

return err;

}

var obj = JSON.parse(v);

var a = obj.param1;

var b = obj.param2;

var i;

// simulate CPU intensive process...

for(i = 0; i < 95550000; ++i)

{

i += 100;

i -= 100;

}

return (a+b).toString();

}

执行node.js,在控制台输入:

var bind  = process.binding('pt_c');

var obj = {param1: 123,param2: 456};

bind.jstask('globalFunction', JSON.stringify(obj), function(err, data){if(err) console.log("err"); else console.log(data);});

调用的方法是bind.jstask,稍后会解释这个函数的使用方法。

下面是測试结果:

上面这个实验操作过程例如以下:

1 首先绑定’pt_c’内建模块

2 高速多次调用backgroundjs中的CPU耗时函数,上面的实验中连续调用了三次。

当backgroundjs中的函数完毕后,主线程接到通知,在新一轮的evenloop中,调用回调函数,打印出结果。这个实验说明了CPU耗时操作异步运行。

方法jstask总共三个參数,前两个參数为字符串,各自是background.js中的全局函数名称,传给函数的參数。最后一个參数是一个callback函数,异步留给主线程执行。

为什么用字符串做參数?

为了适应各种不同的參数类型,就须要为C++函数提供各种不同的函数实现,这是很受限制的。C++依据函数名获取backgroundjs中的函数然后将參数传递给js。在js中,处理json字符串是很easy的,因此採用字符串,简化了C++的逻辑,js又可以方便的生成和解析參数。相同的理由,backgroundjs中函数的返回值也为json串。

对C++的支持

在苛求性能的场景,’pt_c’同意载入一个.dll 文件到node进程,这个dll文件包括CPU耗时操作。js载入’pt_c’的时候,指定文件名称就可以完毕载入。

代码演示样例:

var bind  = process.binding('pt_c');

bind.registermodule('node_pt_c.dll', 'DllInit', 'Json to Init');

bind.posttask('Func_example', 'Json_Param', function(err, data){if(err) console.log("err"); else console.log(data);});

与backgroundjs相比,载入C++模块多了一个步骤,这个步骤是调用bind.registermodule。这个函数负责将载入dll并负责对其初始化。一旦成功后,不能再载入其它模块。全部的CPU耗时操作函数都应该在这个dll文件里实现。

总结

这篇文章提出了backgroundjs这个新的概念,扩展了Node.js的能力,攻克了Node在处理CPU密集任务时的短板。这个解决方式使得使用Node的开发者仅仅须要关注backgroundjs中的函数。比起多开进程或者新加入�模块的解决方式更高效,通用和一致。

我们的代码已经开源,您能够在 https://github.com/classfellow/node/tree/Ansy-CPU-intensive-work--in-one-process

下载。

支持backgroundjs一个稳定Node版本号您能够在

http://www.witch91.com/nodejs.rar

下载。

參考文献:

1 Node.js软肋之CPU密集型任务

http://www.infoq.com/cn/articles/nodejs-weakness-cpu-intensive-tasks/

2  Why you should use Node.js for CPU-bound tasks,Neil Kandalgaonkar,2013.4.30;

3  http://nikhilm.github.io/uvbook/threads.html#inter-thread-communication

Node.js异步处理CPU密集型任务的更多相关文章

  1. 转:Node.js异步处理CPU密集型任务的新思路

    原文来自于:http://www.infoq.com/cn/articles/new-idea-of-nodejs-asynchronous-processing-tasks?utm_source=i ...

  2. 深入理解node.js异步编程:基础篇

    ###[本文是基础内容,大神请绕道,才疏学浅,难免纰漏,请各位轻喷] ##1. 概述 目前开源社区最火热的技术当属Node.js莫属了,作为使用Javascript为主要开发语言的服务器端编程技术和平 ...

  3. node.js异步编程的几种模式

    Node.js异步编程的几种模式 以读取文件为例: 1.callback function const fs = require('fs'); //callback function fs.readF ...

  4. node.js异步编程解决方案之Promise用法

    node.js异步编程解决方案之Promise var dbBase = require('../db/db_base'); var school_info_db = require('../db/s ...

  5. Node.js 异步模式浅析

    注:此文是node.js实战读后的总结. 在平常的脚本语言中都是同步进行的,比如php,服务器处理多个请求的方法就是并行这些脚本.多任务处理,多线程等等.但是这种处理方式也有一个问题:每一个进程或者线 ...

  6. node.js 异步式I/O 与事件驱动

    Node.js 最大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式.这种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一 ...

  7. Node.js 异步异闻录

    本文首发在个人博客:http://muyunyun.cn/posts/7b9fdc87/ 提到 Node.js, 我们脑海就会浮现异步.非阻塞.单线程等关键词,进一步我们还会想到 buffer.模块机 ...

  8. Node.js异步IO原理剖析

    为什么要异步I/O? 从用户体验角度讲,异步IO可以消除UI阻塞,快速响应资源 JavaScript是单线程的,它与UI渲染共用一个线程.所以在JavaScript执行的时候,UI渲染将处于停顿的状态 ...

  9. node js 异步运行流程控制模块Async介绍

    1.Async介绍 sync是一个流程控制工具包.提供了直接而强大的异步功能.基于Javascript为Node.js设计,同一时候也能够直接在浏览器中使用. Async提供了大约20个函数,包含经常 ...

随机推荐

  1. 从UI Automation看Windows平台自动化测试原理

    前言 楼主在2013年初研究Android自动化测试的时候,就分享了几篇文章 Android ViewTree and DecorView Android自动化追本溯源系列(1): 获取页面元素 An ...

  2. AVR GCC对端口的操作指南

    1. AVR GCC for AVR I.I/O端口API1. BV用法:BV(pos);说明:将位定义转换成屏蔽码(MASK).与头文件io.h里的位定义一起使用.例如,置位WDTOE和WDE可表示 ...

  3. Umbraco TextBoxFor 如何加样式和属性

    前些天一直在找免费的CMS开源代码,搜索了很多,大都是介绍CMS开源系统的的文章或者是安装的主要流程.再深的也有但是都是很多年前的文章.我一个英语半吊子加MVC零基础只能像缓慢爬行的蜗牛一步步走了.为 ...

  4. UFLDL教程(四)之Softmax回归

    关于Andrew Ng的machine learning课程中,有一章专门讲解逻辑回归(Logistic回归),具体课程笔记见另一篇文章. 下面,对Logistic回归做一个简单的小结: 给定一个待分 ...

  5. Unity干中学——如何实现游戏截图?

    using UnityEngine; using System.Collections; using System.IO; public class ScreenShot : MonoBehaviou ...

  6. BZOJ 3992 序列统计

    Description 小C有一个集合\(S\),里面的元素都是小于\(M\)的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为\(N\)的数列,数列中的每个数都属于集合\(S\). 小C用 ...

  7. 【POJ3691】 DNA repair (AC自动机+DP)

    DNA repair Time Limit: 2000MS Memory Limit: 65536KB 64bit IO Format: %I64d & %I64u Description B ...

  8. Hadoop上结合opencv\javacv

    mac上安装opencv 1. 去 http://opencv.org 下载最新版OpenCV for Linux/Mac源文件,目前版本是2.4.3.下载后解压.2. 去 http://www.cm ...

  9. 建立HttpsConnection

    1建立HttpConnection,这种连接比较简单,但是是不安全的,网上例子比较多,现在主要说说如果建立HttpsConnection,这种连接时通过SSL协议加密,相对更安全,一般使用这种连接传输 ...

  10. Linq中小心使用IndexOf

      我们平常在做字符串的模糊查询时,有可能会用到下面的类似LINQ写法: string.IsNullOrEmpty(_SN) ? true : a.SN.IndexOf(_SN) != -1   这条 ...