前言

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. SQL Server 2012 Express LocalDB 的作用

    微软最新推出的 SQL Server 2012 Express LocalDB 是一种 SQL Server Express 的运行模式,特别适合用在开发环境使用,也内置在 Visual Studio ...

  2. sphinx 配置文件全解析(转)

    sphinx的配置文件是在配置的时候最容易出错的了: 我们先要明白几个概念: source:数据源,数据是从什么地方来的. index:索引,当有数据源之后,从数据源处构建索引.索引实际上就是相当于一 ...

  3. Ruby gem 更换国内源

    gem sources --add http://gems.ruby-china.org/ --remove https://rubygems.org/

  4. GCC中文错误提示

    最近在教人学c语言,英语不过关,想把ubuntu16.04的gcc改为中文提示,经查找后发现:目前(2016.8.5)基于gcc5.4版本的中文帮助好像还没有. 后来又仔细查找,现在最新的有中文的也就 ...

  5. PYNQ = Python + ZYNQ —— ZYNQ部分功能的Python化

    PYNQ优点:1.    Python用于ZYNQ开发,Python库和FPGA硬件库可以直接调用,极大加快开发进程.缩短开发周期.降低开发难度,更方便.快捷:2.    用PYNQ开发,当Pytho ...

  6. Re-ID with Triplet Loss

    一篇讲Person Re-ID的论文,与人脸识别(认证)有非常多相通的地方. 文章链接: <In Defense of the Triplet Loss for Person Re-Identi ...

  7. jquery ajax异步提交表单数据的方法

    使用jquery的ajax方法可以异步提交表单,成功后后台返回json数据,回调函数处理,可以不用刷新页面,达到异步的目的: 处理表单的数据可以用serialize()方法进行序列化,而如果提交的数据 ...

  8. c++友元函数之---一种特殊的友情

    类可以允许其他类或者函数访问它的私有成员,方法是令其他类或者函数成为它的友元.如果类想把一个函数或者类声明成它的友元,只需要增加一条以friend关键字开始的声明语句即可. 友元声明只能出现在类定义的 ...

  9. 成果展示-RSA算法工具

  10. multi-mechanize error: can not find test script: v_user.py问题

    从github上下载,安装multi-mechanize,新建工程,运行工程报错. 环境: win7-x64, python 2.7 multi-mechanize can not find test ...