前言

Node.js是一种单线程的编程模型,对Node.js的赞美和诟病的也都是因为它的单线程模型,所有的任务都在一个线程中完成(I/O等例外)。单线程模型,不仅让代码非常简洁,更是直接避免了线程调度的复杂性;同样也是因为单线程,让CPU密集型计算应用,完全不适用。

在Node.js的内核中,给了我们一种新的选择,通过child_process模块创建新进程,从而实现多核并行计算。

目录

  1. child_process介绍
  2. child_process的基本使用:spawn, exec, execFile, fork

1. child_process介绍

child_process是Node.js的一个十分重要的模块,通过它可以实现创建多进程,以利用单机的多核计算资源。虽然,Nodejs天生是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信,等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

本文仅从使用上对child_process模块进行介绍,对于深入的内容请参考官方文档,https://nodejs.org/api/child_process.html

关于Node.js线程不错的文章:https://cnodejs.org/topic/518b679763e9f8a5424406e9

2. child_process的基本使用

child_process模块,在v0.12.0版本主要包括4个异步进程函数(spawn,exec,execFile,fork)和3个同步进程函数(spawnSync,execFileSync,execSync)。以异步函数中spawn是最基本的创建子进程的函数,其他三个异步函数都是对spawn不同程度的封装。spawn只能运行指定的程序,参数需要在列表中给出,而exec可以直接运行复杂的命令。

比如要运行 du -sh /disk1 命令, 使用spawn函数需要写成spawn(‘du ‘, [‘-sh ‘, ‘/disk1’]),而使用exec函数时,可以直接写成exec(‘du -sh /disk1’)。exec是会先进行Shell语法解析,因此用exec函数可以更方便的使用复杂的Shell命令,包括管道、重定向等。下面我们就针对每个异步函数,进行测试一下。

系统环境

  • Linux Ubuntu 14.04.1 LTS 64bit
  • Nodejs:v0.13.0-pre
  • Npm:1.4.28

创建项目

~ cd /disk1/demo
~ mkdir nodejs-childprocess && cd nodejs-childprocess

2.1 spawn函数

spawn从定义来看,有3个参数。

child_process.spawn(command[, args][, options])
  • command: 只执行的命令
  • args: 参数列表,可输入多的参数
  • options: 环境变量对象

其中环境变量对象包括7个属性:

  • cwd: 子进程的当前工作目录
  • env: 环境变量键值对
  • stdio: 子进程 stdio 配置
  • customFds: 作为子进程 stdio 使用的文件标示符
  • detached: 进程组的主控制
  • uid: 用户进程的ID.
  • gid: 进程组的ID.

首先,我们运行一下,上文提到的du的命令。直接在命令行,运行的结果。

~ du -sh /disk1
582M /disk1

新建文件spawn.js

~ vi spawn.js

var child = require('child_process');
var du = child.spawn('du', ['-sh', '/disk1']);
du.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
du.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
du.on('exit', function (code) {
console.log('child process exited with code ' + code);
});

通过node运行的结果

~ node spawn.js

stdout: 582M    /disk1
child process exited with code 0

输出的结果是一样的,这样我们就可以很方便地以异步的方式调用系统命令了,spawn是不支持callback函数的,通过stream的方式发数据传给主进程,从而实现了多进程之间的数据交换。

这个功能的直接用应用场景就是“系统监控”。在Linux下,我们有很多命令行工具,可以实时监控CPU,内存,IO,网络等数据,那么用Node的child_process模块可以很容易地把这些数据采集的我们自己的应用中。

比如,我们用mpstat命令,监控用户CPU的使用情况。先看看mpstat命令直接使用的效果。

~ mpstat 1
Linux 3.13.0-32-generic (ape3) 03/20/2015 _x86_64_ (4 CPU)
11:45:56 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
11:45:57 AM all 96.50 0.00 3.50 0.00 0.00 0.00 0.00 0.00 0.00 0.00
11:45:58 AM all 96.50 0.00 3.25 0.00 0.25 0.00 0.00 0.00 0.00 0.00
11:45:59 AM all 96.50 0.00 3.50 0.00 0.00 0.00 0.00 0.00 0.00 0.00
11:46:00 AM all 96.50 0.00 3.50 0.00 0.00 0.00 0.00 0.00 0.00 0.00
11:46:01 AM all 96.26 0.00 3.24 0.00 0.25 0.00 0.25 0.00 0.00 0.00
11:46:02 AM all 96.75 0.00 3.25 0.00 0.00 0.00 0.00 0.00 0.00 0.00
11:46:03 AM all 96.51 0.00 3.24 0.00 0.25 0.00 0.00 0.00 0.00 0.00
^C
Average: all 96.50 0.00 3.35 0.00 0.11 0.00 0.04 0.00 0.00 0.00

我们新建文件mpstat.js,读取mpstat命令的数据,然后只输出%usr的数据。

~ vi mpstat.js

var child = require('child_process');
var mpstat = child.spawn('mpstat', ['1']); //Linux 3.13.0-32-generic (ape3) 03/20/2015 _x86_64_ (4 CPU)
//11:27:12 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
//11:27:13 AM all 96.50 0.00 3.50 0.00 0.00 0.00 0.00 0.00 0.00 0.00 var line = 0;
var cols = ["time","day","CPU","%usr","%nice","%sys","%iowait","%irq","%soft","%steal","%guest","%gnice","%idle"];
mpstat.stdout.on('data', function (data) {
var str = data.toString();
if(line > 2) {
var arr = str.split(/\s+/);
console.log(arr[0]+" "+cols[3]+" "+arr[3]);
}else{
line++;
}
});
mpstat.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
mpstat.on('exit', function (code) {
console.log('child process exited with code ' + code);
});

运行程序:

~ node mpstat.js
11:47:57 %usr 96.75
11:47:58 %usr 96.52
11:47:59 %usr 96.75
11:48:00 %usr 96.25
11:48:01 %usr 96.74
11:48:02 %usr 96.51
11:48:03 %usr 96.74
11:48:04 %usr 96.51

这样就完成系统数据的采集,我们可以把采集到的数据存储到数据库中,通过websocket等协议直接输出的浏览器前端进行数据的展示。

2.2 exec函数

exec函数是对spawn的一种友好封装,增加Shell命令解析,可以直接嵌入复杂的命令,比如,管道用法 cat spawn.js exec.js | wc。

我先试用wc命令来统计一下当前目录的文件字数,分别对应3列为 字节数、字数、行数。

~ wc *
9 29 275 exec.js
25 95 878 mpstat.js
16 41 343 spawn.js
50 165 1496 total ~ cat *.js | wc
50 165 1496

接下来,我们使用exec函数,来使用Linux管道命令。

~ vi exec.js

var childProcess = require('child_process');

var ls = childProcess.exec('cat *.js | wc', function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: '+error.code);
}
console.log('Child Process STDOUT: '+stdout);
});

运行程序:

~ node exec.js
Child Process STDOUT: 50 165 1496

输出结果与Linux命令类似,而且用exec时,命令可以写成一个完整的字符串了,不用像spawn函数时分开写成多个参数数组的形式。最后通过一个callback函数来返回,更符合JavaScript的函数调用习惯,通常情况我们可以用exec来替换spawn的使用。

2.3 execFile函数

execFile函数会直接执行特定的程序,参数作为数组传入,不会被bash解释,因此具有较高的安全性。execFile与spawn的参数相似,也需要分别指定执行的命令和参数,但可以接受一个回调函数,与exec的回调函数相同。

我们看一个execFile函数的例子。

~ vi execFile.js

var childProcess = require('child_process');
var path = ".";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
console.log(result)
});

运行程序

~ node execFile.js
total 16
-rw-r--r-- 1 root root 527 Mar 20 13:23 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js

那么,什么时候使用exec,什么时候使用execFile呢?

如果命令参数是由用户来输入的,对于exec函数来说是有安全性风险的,因为Shell会运行多行命令,比如’ls -l .;pwd,如逗号分隔,之后的命令也会被系统运行。但使用exeFile命令时,命令和参数分来,防止了参数注入的安全风险。

我们用程序测试一下。

~ vi execFile.js

// exec
var cmd = 'ls -l .;pwd'
var ls = childProcess.exec(cmd, function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: '+error.code);
}
console.log('Child Process STDOUT: '+stdout);
}); // execFile
var path = ".;pwd";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
console.log(result)
});

运行程序

~  node execFile.js

{ [Error: Command failed: /bin/ls -l .;pwd
/bin/ls: cannot access .;pwd: No such file or directory
] killed: false, code: 2, signal: null, cmd: '/bin/ls -l .;pwd' } Child Process STDOUT: total 16
-rw-r--r-- 1 root root 547 Mar 20 13:31 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js
/disk1/demo/nodejs-childprocess

从输出结果看到,exec函数被正常执行,而execFile函数,则提示参数错误。

2.4 fork

fork函数,用于在子进程中运行的模块,如 fork(‘./son.js’) 相当于 spawn(‘node’, [‘./son.js’]) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

我们一个主进程和子进程通信的例子,主进程文件main.js和子进程文件son.js。

新建主进程文件。

~ main.js

var childProcess = require('child_process');
var n = childProcess.fork('./son.js'); n.on('message', function(m) {
console.log('Main Listen: ', m);
});
n.send({ hello: 'son' });

新建子进程文件。

~ vi son.js

process.on('message', function(m) {
console.log('Son Listen:', m);
});
process.send({ Hello: 'conan' });

运行程序:

~ node main.js
Main Listen: { Hello: 'conan' }
Son Listen: { hello: 'son' }

通过main.js启动子进程son.js,通过process在两个进程之间传递数据。我们对系统进程进行检查,看看是否确实有两个进程。

~ ps -aux|grep node
root 22777 0.2 0.1 603240 13252 pts/3 Sl+ 14:25 0:00 node main.js
root 22782 0.2 0.1 603540 13516 pts/3 Sl+ 14:25 0:00 /usr/local/bin/node ./son.js

有2个node进程分别是node main.js和node ./son.js。

掌握了进程之间的通信,我们可以做的事情就比较多了,比如自己做一个Node.js多进程管理器,调度器之类。相对于Java的多线程管理或者进程调度来说,Node程序是如此简单明了的。我已经非常明显的感觉到了编程语言在进步!!

来自:http://blog.fens.me/nodejs-child-process/

Node.js进程通信模块child_process的更多相关文章

  1. node.js(七) 子进程 child_process模块

    众所周知node.js是基于单线程模型架构,这样的设计可以带来高效的CPU利用率,但是无法却利用多个核心的CPU,为了解决这个问题,node.js提供了child_process模块,通过多进程来实现 ...

  2. Node.js进程管理之子进程

    一.理论 之前看多进程这一章节时发现这块东西挺多,写Process模块的时候也有提到,今天下午午休醒来静下心来好好的看了一遍,发现也不是太难理解. Node.js是单线程的,对于现在普遍是多处理器的机 ...

  3. Node.js进程管理之Process模块

    在前面Node.js事件运行机制也有提到,Node.js应用在单个线程运行,但是现在大部分服务器都是多处理器,为了方便使用多个进程,Node.js提供了3个模块.Process模块提供了访问正在运行的 ...

  4. 深入理解 Node.js 进程与线程

    原文链接: https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651557398&idx=1&sn=1fb991da ...

  5. 避免uncaughtException错误引起node.js进程崩溃

    uncaughtException 未捕获的异常, 当node.js 遇到这个错误,整个进程直接崩溃. 或许这俩个人上辈子一定是一对冤家. 或许这俩个人经历了前世500次的回眸才换来了今生的相遇,只可 ...

  6. Node.js 进程

    process 是全局对象,能够在任意位置访问,是 EventEmitter 的实例. 退出状态码 当没有新的异步的操作等待处理时,Node 正常情况下退出时会返回状态码 0 .下面的状态码表示其他状 ...

  7. 拿什么守护你的Node.JS进程: Node出错崩溃了怎么办? foreverjs, 文摘随笔

    守护进程 方案一 npm install forever https://github.com/foreverjs/forever 方案二 npm install -g supervisor http ...

  8. Process Node.js 进程

    Process 进程 process.argv 是命令行参数数组,第一个元素是node,第二个元素是脚本文件名,从第三个元素开始每个元素是一个运行参数. process.stdout 标准输出流 co ...

  9. Node.js 进程平滑离场剖析

    本文由云+社区发表 作者:草小灰 使用 Node.js 搭建 HTTP Server 已是司空见惯的事.在生产环境中,Node 进程平滑重启直接关系到服务的可靠性,它的重要性不容我们忽视.既然是平滑重 ...

随机推荐

  1. MySQL数据分组GROUP BY 和HAVING

    对于分组的理解,可以这样:对GROUP BY子句后面跟随的列名进行分组,然后对每一个分组而不是整个表进行操作. 举例如下:在产品表中,检索每一个供应商提供的商品的数量. mysql> SELEC ...

  2. Java:HttpClient篇,Cookie概述,及其在HttpClient4.2中的应用

    1. Cookie 概述 Cookie是什么? Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递.Cookie 包含每次用户访问站点时 Web 应用程序都可以读取 ...

  3. 把Xilinx的IPCORE解密成源代码的方法

    把Xilinx的IPCORE解密成源代码的方法   1.加密的文件格式以can_v1_5/can_tl_bsp.vhd为例子a)前8个字节XlxV38EB是加密的版本号,没研究过其他加密版本,不知道有 ...

  4. sed匹配两种重要思想

    1,sed匹配两种重要思想 屏蔽思想:遮住不想要的 挑出思想:遮住所有,挑出想要的. 2,实例 屏蔽思想: [root@lanny ~]# sed -n '2p' file.txt |sed -r ' ...

  5. Java IP地址字符串与BigInteger的转换, 支持IPv6

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 3 ...

  6. Angularjs 源码

    /** * @license AngularJS v1.3.0-beta.15 * (c) 2010-2014 Google, Inc. http://angularjs.org function t ...

  7. 获取不到Repeater控件中的CheckBox选中状态

    写在前面的话:在做一个项目的时候,需要使用到Repeater控件,并且在Repeater控件内放置了CheckBox控件来标志需要删除的行,选中后,在后台取到的CheckBox的值总是为false.最 ...

  8. CPAN镜像使用帮助

    https://lug.ustc.edu.cn/wiki/mirrors/help/cpan ************************************************** 使用 ...

  9. lambda续集——2

    隐式捕获: 出了显式列出我们希望使用的来自函数的变量外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量.为了指示编译器推断捕获列表,应在捕获列表中写一个&或=.&告诉 ...

  10. dp之多维背包hdu4501

    思路:将v1,v2,k都当作一种体积,每种物品只能取一次,求max....... 反思:以前写背包,由于只有一个体积,所以习惯性的在for中,就所取的最小值限制,而在这次,因为这里导致wa了,具体是因 ...