本文主要给大家介绍了关于Node.js中child_process模块的相关内容,在介绍child_process模块之前,先来看一个例子。

const http = require(‘http’);
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e10; i++) {
sum += i;
};
return sum;
};
const server = http.createServer();
server.on(‘request’, (req, res) => {
if (req.url === ‘/compute’) {
const sum = longComputation();
return res.end(Sum is ${sum});
} else {
res.end(‘Ok’)
}
});

server.listen(3000);
可以试一下使用上面的代码启动Node.js服务,然后打开两个浏览器选项卡分别访问/compute和/,可以发现node服务接收到/compute请求时会进行大量的数值计算,导致无法响应其他的请求(/)。
在Java语言中可以通过多线程的方式来解决上述的问题,但是Node.js在代码执行的时候是单线程的,那么Node.js应该如何解决上面的问题呢?其实Node.js可以创建一个子进程执行密集的cpu计算任务(例如上面例子中的longComputation)来解决问题,而child_process模块正是用来创建子进程的。
创建子进程的方式
child_process提供了几种创建子进程的方式

异步方式:spawn、exec、execFile、fork
同步方式:spawnSync、execSync、execFileSync
首先介绍一下spawn方法

child_process.spawn(command[, args][, options])

command: 要执行的指令
args: 传递参数
options: 配置项
?

const { spawn } = require(‘child_process’);
const child = spawn(‘pwd’);
pwd是shell的命令,用于获取当前的目录,上面的代码执行完控制台并没有任何的信息输出,这是为什么呢?
控制台之所以不能看到输出信息的原因是由于子进程有自己的stdio流(stdin、stdout、stderr),控制台的输出是与当前进程的stdio绑定的,因此如果希望看到输出信息,可以通过在子进程的stdout 与当前进程的stdout之间建立管道实现

child.stdout.pipe(process.stdout);
也可以监听事件的方式(子进程的stdio流都是实现了EventEmitter API的,所以可以添加事件监听)
?

child.stdout.on(‘data’, function(data) {
process.stdout.write(data);
});
在Node.js代码里使用的console.log其实底层依赖的就是process.stdout
除了建立管道之外,还可以通过子进程和当前进程共用stdio的方式来实现
?

const { spawn } = require(‘child_process’);
const child = spawn(‘pwd’, {
stdio: ‘inherit’
});
stdio选项用于配置父进程和子进程之间建立的管道,由于stdio管道有三个(stdin, stdout, stderr)因此stdio的三个可能的值其实是数组的一种简写

pipe 相当于[‘pipe’, ‘pipe’, ‘pipe’](默认值)
ignore 相当于[‘ignore’, ‘ignore’, ‘ignore’]
inherit 相当于[process.stdin, process.stdout, process.stderr]
由于inherit方式使得子进程直接使用父进程的stdio,因此可以看到输出
ignore用于忽略子进程的输出(将/dev/null指定为子进程的文件描述符了),因此当ignore时child.stdout是null。
spawn默认情况下并不会创建子shell来执行命令,因此下面的代码会报错

const { spawn } = require(‘child_process’);
const child = spawn(‘ls -l’);
child.stdout.pipe(process.stdout);

// 报错
events.js:167
throw er; // Unhandled ‘error’ event
^

Error: spawn ls -l ENOENT
at Process.ChildProcess._handle.onexit (internal/child_process.js:229:19)
at onErrorNT (internal/child_process.js:406:16)
at process._tickCallback (internal/process/next_tick.js:63:19)
at Function.Module.runMain (internal/modules/cjs/loader.js:746:11)
at startup (internal/bootstrap/node.js:238:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)
Emitted ‘error’ event at:
at Process.ChildProcess._handle.onexit (internal/child_process.js:235:12)
at onErrorNT (internal/child_process.js:406:16)
[… lines matching original stack trace …]
at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)
如果需要传递参数的话,应该采用数组的方式传入

const { spawn } = require(‘child_process’);
const child = spawn(‘ls’, [’-l’]);
child.stdout.pipe(process.stdout);
如果要执行ls -l | wc -l命令的话可以采用创建两个spawn命令的方式

const { spawn } = require(‘child_process’);
const child = spawn(‘ls’, [’-l’]);
const child2 = spawn(‘wc’, [’-l’]);
child.stdout.pipe(child2.stdin);
child2.stdout.pipe(process.stdout);
也可以使用exec

const { exec } = require(‘child_process’);
exec(‘ls -l | wc -l’, function(err, stdout, stderr) {
console.log(stdout);
});
由于exec会创建子shell,所以可以直接执行shell管道命令。spawn采用流的方式来输出命令的执行结果,而exec也是将命令的执行结果缓存起来统一放在回调函数的参数里面,因此exec只适用于命令执行结果数据小的情况。
其实spawn也可以通过配置shell option的方式来创建子shell进而支持管道命令,如下所示
?

const { spawn, execFile } = require(‘child_process’);
const child = spawn(‘ls -l | wc -l’, {
shell: true
});
child.stdout.pipe(process.stdout);
配置项除了stdio、shell之外还有cwd、env、detached等常用的选项
cwd用于修改命令的执行目录
?

const { spawn, execFile, fork } = require(‘child_process’);
const child = spawn(‘ls -l | wc -l’, {
shell: true,
cwd: ‘/usr’
});
child.stdout.pipe(process.stdout);
env用于指定子进程的环境变量(如果不指定的话,默认获取当前进程的环境变量)
?

const { spawn, execFile, fork } = require(‘child_process’);
const child = spawn(‘echo $NODE_ENV’, {
shell: true,
cwd: ‘/usr’
});
child.stdout.pipe(process.stdout);
NODE_ENV=randal node b.js

// 输出结果
randal
如果指定env的话就会覆盖掉默认的环境变量,如下
?

const { spawn, execFile, fork } = require(‘child_process’);
spawn(‘echo $NODE_TEST $NODE_ENV’, {
shell: true,
stdio: ‘inherit’,
cwd: ‘/usr’,
env: {
NODE_TEST: ‘randal-env’
}
});

NODE_ENV=randal node b.js

// 输出结果
randal
detached用于将子进程与父进程断开连接
例如假设存在一个长时间运行的子进程
?

// timer.js
while(true) {

}
但是主进程并不需要长时间运行的话就可以用detached来断开二者之间的连接
?

const { spawn, execFile, fork } = require(‘child_process’);
const child = spawn(‘node’, [‘timer.js’], {
detached: true,
stdio: ‘ignore’
});
child.unref();
当调用子进程的unref方法时,同时配置子进程的stdio为ignore时,父进程就可以独立退出了
execFile与exec不同,execFile通常用于执行文件,而且并不会创建子shell环境
fork方法是spawn方法的一个特例,fork用于执行js文件创建Node.js子进程。而且fork方式创建的子进程与父进程之间建立了IPC通信管道,因此子进程和父进程之间可以通过send的方式发送消息。
注意:fork方式创建的子进程与父进程是完全独立的,它拥有单独的内存,单独的V8实例,因此并不推荐创建很多的Node.js子进程
fork方式的父子进程之间的通信参照下面的例子
parent.js
?

const { fork } = require(‘child_process’);
const forked = fork(‘child.js’);
forked.on(‘message’, (msg) => {
console.log(‘Message from child’, msg);
});
forked.send({ hello: ‘world’ });
child.js
?

process.on(‘message’, (msg) => {
console.log(‘Message from parent:’, msg);
});

let counter = 0;

setInterval(() => {
process.send({ counter: counter++ });
}, 1000);
?

node parent.js

// 输出结果
Message from parent: { hello: ‘world’ }
Message from child { counter: 0 }
Message from child { counter: 1 }
Message from child { counter: 2 }
Message from child { counter: 3 }
Message from child { counter: 4 }
Message from child { counter: 5 }
Message from child { counter: 6 }
回到本文初的那个问题,我们就可以将密集计算的逻辑放到单独的js文件中,然后再通过fork的方式来计算,等计算完成时再通知主进程计算结果,这样避免主进程繁忙的情况了。
compute.js
?

const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e10; i++) {
sum += i;
};
return sum;
};

process.on(‘message’, (msg) => {
const sum = longComputation();
process.send(sum);
});
index.js
?

const http = require(‘http’);
const { fork } = require(‘child_process’);
const server = http.createServer();
server.on(‘request’, (req, res) => {
if (req.url === ‘/compute’) {
const compute = fork(‘compute.js’);
compute.send(‘start’);
compute.on(‘message’, sum => {
res.end(Sum is ${sum});
});
} else {
res.end(‘Ok’)
}
});
server.listen(3000);
监听进程事件
通过前述几种方式创建的子进程都实现了EventEmitter,因此可以针对进程进行事件监听
常用的事件包括几种:close、exit、error、message
close事件当子进程的stdio流关闭的时候才会触发,并不是子进程exit的时候close事件就一定会触发,因为多个子进程可以共用相同的stdio。
close与exit事件的回调函数有两个参数code和signal,code代码子进程最终的退出码,如果子进程是由于接收到signal信号终止的话,signal会记录子进程接受的signal值。
先看一个正常退出的例子
?

const { spawn, exec, execFile, fork } = require(‘child_process’);
const child = exec(‘ls -l’, {
timeout: 300
});
child.on(‘exit’, function(code, signal) {
console.log(code);
console.log(signal);
});

// 输出结果
0
null
再看一个因为接收到signal而终止的例子,应用之前的timer文件,使用exec执行的时候并指定timeout

const { spawn, exec, execFile, fork } = require(‘child_process’);
const child = exec(‘node timer.js’, {
timeout: 300
});
child.on(‘exit’, function(code, signal) {
console.log(code);
console.log(signal);
});
// 输出结果
null
SIGTERM
注意:由于timeout超时的时候error事件并不会触发,并且当error事件触发时exit事件并不一定会被触发
error事件的触发条件有以下几种:

无法创建进程
无法结束进程
给进程发送消息失败
注意当代码执行出错的时候,error事件并不会触发,exit事件会触发,code为非0的异常退出码
?

const { spawn, exec, execFile, fork } = require(‘child_process’);
const child = exec(‘ls -l /usrs’);
child.on(‘error’, function(code, signal) {
console.log(code);
console.log(signal);
});
child.on(‘exit’, function(code, signal) {
console.log(‘exit’);
console.log(code);
console.log(signal);
});

// 输出结果
exit
1
null
message事件适用于父子进程之间建立IPC通信管道的时候的信息传递,传递的过程中会经历序列化与反序列化的步骤,因此最终接收到的并不一定与发送的数据相一致。
sub.js
?
1
process.send({ foo: ‘bar’, baz: NaN });
?

const cp = require(‘child_process’);
const n = cp.fork(${__dirname}/sub.js);

n.on(‘message’, (m) => {
console.log(‘got message:’, m); // got message: { foo: ‘bar’, baz: null }
});
关于message有一种特殊情况要注意,下面的message并不会被子进程接收到
?

const { fork } = require(‘child_process’);
const forked = fork(‘child.js’);
forked.send({
cmd: “NODE_foo”,
hello: ‘world’
});
当发送的消息里面包含cmd属性,并且属性的值是以NODE_开头的话,这样的消息是提供给Node.js本身保留使用的,因此并不会发出message事件,而是会发出internalMessage事件,开发者应该避免这种类型的消息,并且应当避免监听internalMessage事件。
message除了发送字符串、object之外还支持发送server对象和socket对象,正因为支持socket对象才可以做到多个Node.js进程监听相同的端口号。

原文:https://www.jb51.net/article/141698.htm

Node.js中的child_process模块详解相关推荐

  1. python中json模块博客园_Python中的Json模块详解

    Python中的Json模块详解 Json(JavaScript Object Notation)它是一种轻量级的数据交换格式,具有数据格式简单,读写方便易懂等很多优点.许多主流的编程语言都在用它来进 ...

  2. spring(7)---深入理解Spring核心技术——Spring中的各模块详解

    深入理解Spring核心技术--Spring中的各模块详解 Spring框架的两个基本概念IOC容器和AOP,相信大家现在对Spring中的这两个部分的基本概念有了一定的认识,好了,那么今天我们就来正 ...

  3. python中 xlrd/xlwt模块详解

    python中 xlrd/xlwt模块详解 1.什么是xlrd模块 python操作excel主要用到xlrd和xlwt两个库,即xlrd是读excel,xlwt是写excel库 一.安装xlrd模块 ...

  4. JS中的event 对象详解

    JS中的event 对象详解 JS的event对象 Event属性和方法: 1. type:事件的类型,如onlick中的click: 2. srcElement/target:事件源,就是发生事件的 ...

  5. js中indexOf的用法详解

    js中indexOf的用法详解 String.IndexOf 方法 (Char, [startIndex], [count]) 报告指定字符在此实例中的第一个匹配项的索引.搜索从指定字符位置开始,并检 ...

  6. JS 中 valueOf() 方法的详解

    JS 中 valueOf() 方法的详解 JavaScript 中的 valueOf() 方法用于返回指定对象的原始值,若对象没有原始值,则将返回对象本身.通常由JavaScript内部调用,而不是在 ...

  7. 如何在Node JS中卸载NPM模块?

    本文翻译自:How to uninstall npm modules in node js? As commonly known, any npm module can be installed by ...

  8. 显式等待中的EC模块详解

    导入模块 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common ...

  9. JS中的加号+运算符详解

    加号+运算符 在 JavaScript 中,加法的规则其实很简单,只有两种情况: 把数字和数字相加 把字符串和字符串相加 所有其他类型的值都会被自动转换成这两种类型的值. 为了能够弄明白这种隐式转换是 ...

最新文章

  1. java集合框架史上最详解(list set 以及map)
  2. VS调试技巧之附加进程
  3. 蓝桥杯之第几个幸运数
  4. 柔性太阳能电池pdf_房车旅行如何做到电力无忧,那就选择一套合适的太阳能供电系统吧...
  5. c++ 函数返回空_Python all() 函数
  6. 敏捷开发将走向消亡,我们该如何应对
  7. 阿里云服务器上搭建cs远控
  8. 机器翻译市场需求调研报告
  9. 我的世界java骷髅马_教萌新如何在我的世界中拥有骷髅马坐骑
  10. 关键字const有什么含意?
  11. C语言的数学运算函数
  12. NET Reflector——.NET反编译工具
  13. 201671030116宋菲菲 《英文文本统计分析》结对项目报告
  14. safari浏览器的兼容
  15. 爱普生LQ-635K针式打印机打链式打印纸设置自动切纸方法
  16. Visual Studio Code使用教程
  17. StudyNotes_MachineLearning_3(吴恩达机器学习公开课)
  18. spark插件入门完整版本
  19. 【2013Esri中国用户大会】商业智能位置分析之Esri Maps for Cognos
  20. 大数据解决的核心问题

热门文章

  1. 手把手讲解 Android Hook-Activity的启动流程
  2. 2.3Linux常用命令
  3. nginx的access.log文件详解,main的具体意思
  4. 高德地图根绝经纬度画线跑步软件
  5. 矩阵乘法 基础训练-蓝桥杯
  6. 时区缩写 UTC, CST, GMT, CEST 以及转换
  7. 聊聊WEB项目中的图片
  8. Leetcode 576. 出界的路径数
  9. Chrome插件实现GitHub代码离线翻译v0.0.4 2018-10-19
  10. 18天精读掌握《费曼物理学讲义卷一》 第2天 2019.6.13