Node.js 子进程
子进程
Node.js是基于单线程模型架构的,它能够拥有高效的CPU利用率,却限制了多个核心CPU的使用,为此,Node.js提供了child_process
模块以通过多线程来实现对多核CPU的使用。
稳定性: 3 - 稳定
Node通过child_process
模块提供了popen(3)
数据流。
它能在非阻塞的方式中,通过stdin
,stdout
和stderr
传递数据。
请注意:某些程序使用内部线性缓冲I/O, 它并不妨碍node.js,只是你发送给子进程的数据不会被立即取消。
你可以使用require('child_process').spawn()
或require('child_process').fork()
创建一个子进程。这两种方法有区别,在下文中将进行解释。
开发过程中查看synchronous counterparts效率会更高。
类: ChildProcess
ChildProcess
是一个EventEmitter。
子进程有三个相关的流child.stdin
,child.stdout
和child.stderr
。他们可能和会父进程的stdiostreams共享,也可作为独立的对象。
不能直接调用ChildProcess类,使用spawn()
,exec()
,execFile()
或fork()
方法来创建子进程的实例。
事件: 'error'
err
{Error Object}错误。
发生于:
- 无法创建进程。
注意:exit
事件有可能在错误发生后调用,也可能不调用,所以如果你监听这两个事件来触发函数,记得预防函数会被调用2次。
参考ChildProcess#kill()
和ChildProcess#send()
。
事件: 'exit'
code
{Number} 退出代码, 正常退出时才有效。
子进程结束的时候触发这个事件。如果子进程正常终止,则code
为最终的退出代码,否则为null
。如果是由signal
引起的终止,则signal
为字符串,否则为 null
。
注意:子进程的stdio流可能仍为开启模式。
注意,node为'SIGINT'
和'SIGTERM
' 建立句柄,所以当信号来临的时候,他们不会终止而是退出。
参靠waitpid(2)
。
事件: 'close'
code
{Number} 退出代码, 正常退出时才有效。
子进程里所有stdio流都关闭时触发这个事件。要和'exit'区分开,因为多进程可以共享一个stdio流。
Event: 'disconnect'
父进程或子进程中调用.disconnect()
方法后触发这个事件。断开后不会在互发消息,并且.connected
属性值为false。
Event: 'message'
message
{Object} 一个解析过的JSON对象,或者一个原始值。
通过.send(message, [sendHandle])
传递消息。
child.stdin
- {Stream object}
子进程的stdin
是Writable Stream
(可写流)。如果子进程在等待输入,它就会暂停直到通过调用end()
来关闭。
child.stdin
是child.stdio[0]
的缩写。这两个都指向同一个对象,或者null。
child.stdout
- {Stream object}
子进程的stdout
是Readable Stream
(可读流)。
child.stdout
是 child.stdio[1]
的缩写。 这两个都指向同一个对象,或者null。
child.stderr
- {Stream object}
子进程的stderr
是Readable Stream
(可写流)。
child.stderr
是child.stdio[2]
缩写。这两个都指向同一个对象,或者null。
child.stdio
- {Array}
子进程的管道数组和spawn的stdio里设置为'pipe'
的内容次序相对应。
注意,流0-2也能分别用ChildProcess.stdin、ChildProcess.stdout和ChildProcess.stderrNote来表示。
在下面的例子里,只有子进程的fd1
设置为pipe管道,所以父进程的child.stdio[1]
是流(stream),数组里其他值为null
。
child = child_process.spawn("ls", {
stdio: [
0, // use parents stdin for child
'pipe', // pipe child's stdout to parent
fs.openSync("err.out", "w") // direct child's stderr to a file
]
}
assert.equal(child.stdio[0], null
assert.equal(child.stdio[0], child.stdin
assert(child.stdout
assert.equal(child.stdio[1], child.stdout
assert.equal(child.stdio[2], null
assert.equal(child.stdio[2], child.stderr
child.pid
- {Integer}
子进程的PID。
例子:
var spawn = require('child_process').spawn,
grep = spawn('grep', ['ssh']
console.log('Spawned child pid: ' + grep.pid
grep.stdin.end(
child.connected
- {Boolean} 调用`.disconnect'后设置为false
如果.connected
为 false,消息不再可用。
child.kill(signal)
signal
{String}
发送信号给子进程。如果没有参数,会发送'SIGTERM'
,参见signal(7)
里的可用的信号列表。
var spawn = require('child_process').spawn,
grep = spawn('grep', ['ssh']
grep.on('close', function (code, signal) {
console.log('child process terminated due to receipt of signal '+signal
}
// send SIGHUP to process
grep.kill('SIGHUP'
当信号无法传递的时候会触发'error'
事件。给已经终止的进程发送信号不会触发'error'
事件,但是可以能引起不可预知的后果: 因为有可能PID (进程ID) 已经重新分配给其他进程,信号就会被发送到新的进程里,无法想象这样会引发什么样的事情。
注意:当函数调用kill
信号的时候,它实际并并不会杀死进程,只是发送信号给进程。
参见kill(2)
child.send(message, sendHandle)
message
{Object}
使用child_process.fork()
的时候,你能用child.send(message, [sendHandle])
给子进程写数据,子进程通过'message'
接收消息。
例如:
var cp = require('child_process'
var n = cp.fork(__dirname + '/sub.js'
n.on('message', function(m) {
console.log('PARENT got message:', m
}
n.send{ hello: 'world' }
子进程的代码'sub.js'
:
process.on('message', function(m) {
console.log('CHILD got message:', m
}
process.send{ foo: 'bar' }
子进程代码里的process
对象拥有send()
方法,当它通过信道接收到信息时会触发,并返回对象。
注意:父进程和子进程send()
是同步的,不要用来发送大块的数据(可以用管道来代替,参见child_process.spawn
)。
不过发送{cmd: 'NODE_foo'}
消息是特殊情况。所有包含NODE_
前缀的消息都不会被触发,因为它们是node的内部的核心消息,它们会在internalMessage
事件里触发,尽量避免使用这个特性。
child.send()
里的sendHandle
属性用来发送TCP服务或socket对象给其他的进程,子进程会用接收到的对象作为message
事件的第二个参数。
如果不能发出消息会触发'error'
事件,比如子进程已经退出。
例子: 发送 server 对象
以下是例子:
var child = require('child_process').fork('child.js'
// Open up the server object and send the handle.
var server = require('net').createServer(
server.on('connection', function (socket) {
socket.end('handled by parent'
}
server.listen(1337, function() {
child.send('server', server
}
子进程将会收到这个server对象:
process.on('message', function(m, server) {
if (m === 'server') {
server.on('connection', function (socket) {
socket.end('handled by child'
}
}
}
注意,现在父子进程共享了server,某些连接会被父进程处理,某些会被子进程处理。
dgram
服务器,工作流程是一样的,监听的是message
事件,而不是connection
,使用server.bind
而不是server.listen
。(目前仅支持UNIX平台)
例子: 发送 socket 对象
以下是发送socket对象的例子。他将会创建2个子线程,并且同时处理连接,一个将远程地址74.125.127.100
当做VIP发送到一个特殊的子进程,另外一个发送到正常进程。var normal=require('child_process').fork('child.js', 'normal'var special = require('child_process').fork('child.js', 'special'
// Open up the server and send sockets to child
var server = require('net').createServer(
server.on('connection', function (socket) {
// if this is a VIP
if (socket.remoteAddress === '74.125.127.100') {
special.send('socket', socket
return;
}
// just the usual dudes
normal.send('socket', socket
}
server.listen(1337
child.js
代码如下:
process.on('message', function(m, socket) {
if (m === 'socket') {
socket.end('You were handled as a ' + process.argv[2] + ' person'
}
}
注意,当socket发送给子进程后,如果这个socket被销毁,父进程不再跟踪它,相应的.connections
属性会变为null
。这种情况下,不建议使用 .maxConnections
。
child.disconnect()
关闭父子进程间的所有IPC通道,能让子进程优雅的退出。调用这个方法后,父子进程里的.connected
标志会变为false
,之后不能再发送消息。
当进程里没有消息需要处理的时候,会触发'disconnect'事件。
注意,在子进程还有IPC通道的情况下(如fork()
),也可以调用process.disconnect()
来关闭它。
创建异步处理
这些方法遵从常用的异步处理模式(比如回调,或者返回一个事件处理)。
child_process.spawn(command, args)
command
{String} 要运行的命令
- 返回: {ChildProcess object}
用指定的command
发布一个子进程,args
是命令行参数。如果忽略,args
是空数组。
第三个参数用来指定附加设置,默认值:
{ cwd: undefined,
env: process.env
}
创建的子进程里使用cwd
指定工作目录,如果没有指定,默认继承自当前的工作目录。
使用env
来指定新进程可见的环境变量。默认是process.env
。
例如,运行ls -lh /usr
,获取stdout
,stderr
和退出代码:
var spawn = require('child_process').spawn,
ls = spawn('ls', ['-lh', '/usr']
ls.stdout.on('data', function (data) {
console.log('stdout: ' + data
}
ls.stderr.on('data', function (data) {
console.log('stderr: ' + data
}
ls.on('close', function (code) {
console.log('child process exited with code ' + code
}
例如:通过一个非常精巧的方法执行'ps ax | grep ssh'
var spawn = require('child_process').spawn,
ps = spawn('ps', ['ax']),
grep = spawn('grep', ['ssh']
ps.stdout.on('data', function (data) {
grep.stdin.write(data
}
ps.stderr.on('data', function (data) {
console.log('ps stderr: ' + data
}
ps.on('close', function (code) {
if (code !== 0) {
console.log('ps process exited with code ' + code
}
grep.stdin.end(
}
grep.stdout.on('data', function (data) {
console.log('' + data
}
grep.stderr.on('data', function (data) {
console.log('grep stderr: ' + data
}
grep.on('close', function (code) {
if (code !== 0) {
console.log('grep process exited with code ' + code
}
}
options.stdio
stdio
可能是以下几个参数之一:
'pipe'
-['pipe', 'pipe', 'pipe']
,默认值
child_process.spawn()
里的'stdio'参数是一个数组,它和子进程的fd相对应,它的值如下:
'pipe'
- 创建在父进程和子进程间的pipe。管道的父进程端以child_process
的属性形式暴露给父进程,例如ChildProcess.stdio[fd]
。为fds 0 - 2创建的管道也可以通过ChildProcess.stdin,ChildProcess.stdout和ChildProcess.stderr来独立的访问。
例如:
var spawn = require('child_process').spawn;
// Child will use parent's stdios
spawn('prg', [], { stdio: 'inherit' }
// Spawn child sharing only stderr
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] }
// Open an extra fd=4, to interact with programs present a
// startd-style interface.
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] }
options.detached
如果设置了detached
选项,子进程将会被作为新进程组的leader,这使得子进程可以在父进程退出后继续运行。
缺省情况下父进程会等detached的子进程退出。要阻止父进程等待一个这样的子进程,调用child.unref()方法,则父进程的事件循环引用计数中将不会包含这个子进程。
detaching一个长期运行的进程,并重新将输出指向文件:
var fs = require('fs'),
spawn = require('child_process').spawn,
out = fs.openSync('./out.log', 'a'),
err = fs.openSync('./out.log', 'a'
var child = spawn('prg', [], {
detached: true,
stdio: [ 'ignore', out, err ]
}
child.unref(
使用detached
选项来启动一个长时间运行的进程时,进程不会在后台保持运行,除非他提供了一个不连接到父进程的stdio
。如果继承了父进程的stdio
,则子进程会继续控制终端。
options.customFds
已废弃,customFds
允许指定特定文件描述符作为子进程的stdio
。该API无法移植到所有平台,因此被废弃。使用customFds
可以将新进程的 stdin
, stdout
,stderr
钩到已有流上;-1表示创建新流。自己承担使用风险。
参见:child_process.exec()
和child_process.fork()
child_process.exec(command, options, callback)
command
{String} 要执行的命令,空格分割
callback
{Function} 进程终止的时候调用
- 返回: ChildProcess对象
在shell里执行命令,并缓冲输出。
var exec = require('child_process').exec,
child;
child = exec('cat *.js bad_file | wc -l',
function (error, stdout, stderr) {
console.log('stdout: ' + stdout
console.log('stderr: ' + stderr
if (error !== null) {
console.log('exec error: ' + error
}
}
回调参数是(error, stdout, stderr)
。如果成功,则,error
值为null
。 如果失败,则error
变为Error
的实例,error.code
等于子进程退出码,并且 error.signal
会被设置为结束进程的信号名。
第二个参数可以设置一些选项。默认如下:
{ encoding: 'utf8',
timeout: 0,
maxBuffer: 200*1024,
killSignal: 'SIGTERM',
cwd: null,
env: null }
如果timeout
大于0,子进程运行时间超过timeout
时会被终止。killSignal
(默认: 'SIGTERM'
)能杀死子进程。maxBuffer
设定了stdout或stderr的最大数据量,如果子进程的数量量超过了,将会被杀死。
(file, args, callback)
file
{String} 要运行的程序的文件名
callback
{Function} 进程终止的时候调用
- 返回: ChildProcess对象
和child_process.exec()
类似,不同之处在于这是执行一个指定的文件,因此它比child_process.exec
精简些,参数相同。
child_process.fork(modulePath, args)
modulePath
{String} 子进程里运行的模块
- 返回: ChildProcess对象
这是spawn()
的特殊例子,用于派生Node进程。除了拥有子进程的所有方法,它的返回对象还拥有内置通讯通道。参见child.send(message, [sendHandle])
。
这些Nodes是全新的V8实例化,假设每个Node最少需要30ms的启动时间,10mb的存储空间,可想而知,创建几千个Node是不太现实的。
options
对象中的execPath
属性可以用于执行文件(非当前node
)创建子进程。这需要小心使用,缺省情况下fd表示子进程的NODE_CHANNEL_FD
环境变量。该fa的输入和输出是以行分割的JSON对象。
创建同步进程
以下这些方法是同步
的,意味着他会阻塞
事件循环,并暂停执行代码,直到spawned的进程退出。
同步方法简化了任务进程,比如大为简化在应用初始化加载/处理过程。
child_process.spawnSync(command, args)
command
{String} 要执行的命令
- 返回: {Object}
spawnSync
直到子进程关闭才会返回。超时或者收到killSignal
信号,也不会返回,直到进程完全退出。进程处理完SIGTERM
信号后并不会结束,直到子进程完全退出。
child_process.execFileSync(command, args)
command
{String} 要执行的命令
- `env` {Object} 环境变量
- `uid` {Number} 设置用户进程的ID。 (参见setuid(2)。)
- `gid` {Number} 设置进程组的ID。 (参见setgid(2)。)
- `timeout` {Number} 进程运行最大毫秒数。 (默认: undefined)
- `killSignal` {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
- `maxBuffer` {Number}
- `encoding` {String} stdio输入和输出的编码方式。 (默认: 'buffer')
- 返回: {Buffer|String} 来自命令的stdout
直到子进程完全退出,execFileSync
才会返回。超时或者收到killSignal
信号,也不会返回,直到进程完全退出。进程处理完SIGTERM
信号后并不会结束,直到子进程完全退出。
如果进程超时,或者非正常退出,这个方法将会抛出异常。Error
会包含整个child_process.spawnSync
结果。
child_process.execSync(command, options)
command
{String} 要执行的命令
- `env` {Object} 环境变量
- `uid` {Number} 设置用户进程的ID。 (参见setuid(2)。)
- `gid` {Number} 设置进程组的ID。 (参见setgid(2)。)
- `timeout` {Number} 进程运行最大毫秒数。 (默认: undefined)
- `killSignal` {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
- `maxBuffer` {Number}
- `encoding` {String} stdio输入和输出的编码方式。 (默认: 'buffer')
- 返回: {Buffer|String}来自命令的stdout
直到子进程完全退出,execSync
才会返回。超时或者收到killSignal
信号,也不会返回,直到进程完全退出。进程处理完SIGTERM
信号后并不会结束,直到子进程完全退出。
如果进程超时,或者非正常退出,这个方法将会抛出异常。Error
会包含整个child_process.spawnSync
结果。
以上就是Node.js官方文档中有关子进程的介绍。