在nodejs中创建child process
在nodejs中创建child process
简介
nodejs的main event loop是单线程的,nodejs本身也维护着Worker Pool用来处理一些耗时的操作,我们还可以通过使用nodejs提供的worker_threads来手动创建新的线程来执行自己的任务。
本文将会介绍一种新的执行nodejs任务的方式,child process。
child process
lib/child_process.js提供了child_process模块,通过child_process我们可以创建子进程。
注意,worker_threads创建的是子线程,而child_process创建的是子进程。
在child_process模块中,可以同步创建进程也可以异步创建进程。同步创建方式只是在异步创建的方法后面加上Sync。
创建出来的进程用ChildProcess类来表示。
我们看下ChildProcess的定义:
interface ChildProcess extends events.EventEmitter {
stdin: Writable | null;
stdout: Readable | null;
stderr: Readable | null;
readonly channel?: Pipe | null;
readonly stdio: [
Writable | null, // stdin
Readable | null, // stdout
Readable | null, // stderr
Readable | Writable | null | undefined, // extra
Readable | Writable | null | undefined // extra
];
readonly killed: boolean;
readonly pid: number;
readonly connected: boolean;
readonly exitCode: number | null;
readonly signalCode: NodeJS.Signals | null;
readonly spawnargs: string[];
readonly spawnfile: string;
kill(signal?: NodeJS.Signals | number): boolean;
send(message: Serializable, callback?: (error: Error | null) => void): boolean;
send(message: Serializable, sendHandle?: SendHandle, callback?: (error: Error | null) => void): boolean;
send(message: Serializable, sendHandle?: SendHandle, options?: MessageOptions, callback?: (error: Error | null) => void): boolean;
disconnect(): void;
unref(): void;
ref(): void;
/**
* events.EventEmitter
* 1. close
* 2. disconnect
* 3. error
* 4. exit
* 5. message
*/
...
}
可以看到ChildProcess也是一个EventEmitter,所以它可以发送和接受event。
ChildProcess可以接收到event有5种,分别是close,disconnect,error,exit和message。
当调用父进程中的 subprocess.disconnect() 或子进程中的 process.disconnect() 后会触发 disconnect 事件。
当出现无法创建进程,无法kill进程和向子进程发送消息失败的时候都会触发error事件。
当子进程结束后时会触发exit事件。
当子进程的 stdio 流被关闭时会触发 close 事件。 注意,close事件和exit事件是不同的,因为多个进程可能共享同一个stdio,所以发送exit事件并不一定会触发close事件。
看一个close和exit的例子:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.on('close', (code) => {
console.log(`子进程使用代码 ${code} 关闭所有 stdio`);
});
ls.on('exit', (code) => {
console.log(`子进程使用代码 ${code} 退出`);
});
最后是message事件,当子进程使用process.send() 发送消息的时候就会被触发。
ChildProcess中有几个标准流属性,分别是stderr,stdout,stdin和stdio。
stderr,stdout,stdin很好理解,分别是标准错误,标准输出和标准输入。
我们看一个stdout的使用:
const { spawn } = require('child_process');
const subprocess = spawn('ls');
subprocess.stdout.on('data', (data) => {
console.log(`接收到数据块 ${data}`);
});
stdio实际上是stderr,stdout,stdin的集合:
readonly stdio: [
Writable | null, // stdin
Readable | null, // stdout
Readable | null, // stderr
Readable | Writable | null | undefined, // extra
Readable | Writable | null | undefined // extra
];
其中stdio[0]表示的是stdin,stdio[1]表示的是stdout,stdio[2]表示的是stderr。
如果在通过stdio创建子进程的时候,这三个标准流被设置为除pipe之外的其他值,那么stdin,stdout和stderr将为null。
我们看一个使用stdio的例子:
const assert = require('assert');
const fs = require('fs');
const child_process = require('child_process');
const subprocess = child_process.spawn('ls', {
stdio: [
0, // 使用父进程的 stdin 用于子进程。
'pipe', // 把子进程的 stdout 通过管道传到父进程 。
fs.openSync('err.out', 'w') // 把子进程的 stderr 定向到一个文件。
]
});
assert.strictEqual(subprocess.stdio[0], null);
assert.strictEqual(subprocess.stdio[0], subprocess.stdin);
assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1], subprocess.stdout);
assert.strictEqual(subprocess.stdio[2], null);
assert.strictEqual(subprocess.stdio[2], subprocess.stderr);
通常情况下父进程中维护了一个对子进程的引用计数,只有在当子进程退出之后父进程才会退出。
这个引用就是ref,如果调用了unref方法,则允许父进程独立于子进程退出。
const { spawn } = require('child_process');
const subprocess = spawn(process.argv[0], ['child_program.js'], {
detached: true,
stdio: 'ignore'
});
subprocess.unref();
最后,我们看一下如何通过ChildProcess来发送消息:
subprocess.send(message[, sendHandle[, options]][, callback])
其中message就是要发送的消息,callback是发送消息之后的回调。
sendHandle比较特殊,它可以是一个TCP服务器或socket对象,通过将这些handle传递给子进程。子进程将会在message事件中,将该handle传递给Callback函数,从而可以在子进程中进行处理。
我们看一个传递TCP server的例子,首先看主进程:
const subprocess = require('child_process').fork('subprocess.js');
// 打开 server 对象,并发送该句柄。
const server = require('net').createServer();
server.on('connection', (socket) => {
socket.end('由父进程处理');
});
server.listen(1337, () => {
subprocess.send('server', server);
});
再看子进程:
process.on('message', (m, server) => {
if (m === 'server') {
server.on('connection', (socket) => {
socket.end('由子进程处理');
});
}
});
可以看到子进程接收到了server handle,并且在子进程中监听connection事件。
下面我们看一个传递socket对象的例子:
onst { fork } = require('child_process');
const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);
// 开启 server,并发送 socket 给子进程。
// 使用 `pauseOnConnect` 防止 socket 在被发送到子进程之前被读取。
const server = require('net').createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {
// 特殊优先级。
if (socket.remoteAddress === '74.125.127.100') {
special.send('socket', socket);
return;
}
// 普通优先级。
normal.send('socket', socket);
});
server.listen(1337);
subprocess.js的内容:
process.on('message', (m, socket) => {
if (m === 'socket') {
if (socket) {
// 检查客户端 socket 是否存在。
// socket 在被发送与被子进程接收这段时间内可被关闭。
socket.end(`请求使用 ${process.argv[2]} 优先级处理`);
}
}
});
主进程创建了两个subprocess,一个处理特殊的优先级, 一个处理普通的优先级。
异步创建进程
child_process模块有4种方式可以异步创建进程,分别是child_process.spawn()、child_process.fork()、child_process.exec() 和 child_process.execFile()。
先看一个各个方法的定义:
child_process.spawn(command[, args][, options])
child_process.fork(modulePath[, args][, options])
child_process.exec(command[, options][, callback])
child_process.execFile(file[, args][, options][, callback])
其中child_process.spawn是基础,他会异步的生成一个新的进程,其他的fork,exec和execFile都是基于spawn来生成的。
fork会生成新的Node.js 进程。
exec和execFile是以新的进程执行新的命令,并且带有callback。他们的区别就在于在windows的环境中,如果要执行.bat或者.cmd文件,没有shell终端是执行不了的。这个时候就只能以exec来启动。execFile是无法执行的。
或者也可以使用spawn。
我们看一个在windows中使用spawn和exec的例子:
// 仅在 Windows 上。
const { spawn } = require('child_process');
const bat = spawn('cmd.exe', ['/c', 'my.bat']);
bat.stdout.on('data', (data) => {
console.log(data.toString());
});
bat.stderr.on('data', (data) => {
console.error(data.toString());
});
bat.on('exit', (code) => {
console.log(`子进程退出,退出码 ${code}`);
});
const { exec, spawn } = require('child_process');
exec('my.bat', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
// 文件名中包含空格的脚本:
const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true });
// 或:
exec('"my script.cmd" a b', (err, stdout, stderr) => {
// ...
});
同步创建进程
同步创建进程可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到子进程退出。
通常对于一些脚本任务来说,使用同步创建进程会比较常用。
本文作者:flydean程序那些事
本文链接:http://www.flydean.com/nodejs-childprocess/
本文来源:flydean的博客
欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
在nodejs中创建child process的更多相关文章
- 在nodejs中创建cluster
目录 简介 cluster集群 cluster详解 cluster中的event cluster中的方法 cluster中的属性 cluster中的worker 总结 在nodejs中创建cluste ...
- 在Salesforce中创建Approval Process
在Salesforce中可以创建Approval Process来实现审批流程的功能,实际功能与我们常说的Workflow很相似,具体的设置步骤如下所示 1):选择对应的Object去创建对应的App ...
- nodejs中创建web服务,监听本地IP
nodejs官网例子 var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {' ...
- nodeJs中创建服务器
var http=require('http'); var httpObj=http.createServer(function(req,res) { console.log('someBody:' ...
- node中非常重要的process对象,Child Process模块
node中非常重要的process对象,Child Process模块Child Process模块http://javascript.ruanyifeng.com/nodejs/child-proc ...
- nodejs中使用worker_threads来创建新的线程
目录 简介 worker_threads isMainThread MessageChannel parentPort和MessagePort markAsUntransferable SHARE_E ...
- Nodejs express中创建ejs项目,解决express下默认创建jade,无法创建ejs问题
最近在看<Node.js开发指南>,看到使用nodejs进行web开发的时候,准备创建ejs项目遇到问题了, 书上命令为: express -t ejs microblog 可是执行后,仍 ...
- Nodejs express中创建ejs项目 error install Couldn't read dependencies
最近在看<Node.js开发指南>,看到使用nodejs进行web开发的时候,准备创建ejs项目遇到问题了 书上命令为: express -t ejs microblog 可是执行 ...
- [NodeJs系列][译]理解NodeJs中的Event Loop、Timers以及process.nextTick()
译者注: 为什么要翻译?其实在翻译这篇文章前,笔者有Google了一下中文翻译,看的不是很明白,所以才有自己翻译的打算,当然能力有限,文中或有错漏,欢迎指正. 文末会有几个小问题,大家不妨一起思考一下 ...
随机推荐
- MySQL管理基础
#1.数据库连接管理 mysql命令说明 第一个功能:连接数据库(在前面mysql命令的使用里面讲解了,这里就不讲解了) 第二个功能:mysql客户端自带的命令功能 mysql命令的使用(mysql ...
- H3C路由器配置——静态路由
一.网络畅通条件及排错思路 1.网络畅通的条件 网络畅通的条件:数据包能去能回,也是我们排除网络故障的理论依据. 2.网络不畅通示列 ①.目标主机不可达 原因分析:可能是数据包没有到达目的地,在中途就 ...
- python初学者-判断一个数是否为素数
while True: #判断为真 num = int(input('请输入一个数:')) for i in range(2,num):#判断在num之前的数能不能把num整除 if(num%i == ...
- 本地H5模式写的APP体验可以比APP还好
很多APP使用H5编写,但APP链接的是远程的url的模式,导致了APP的用户体验极差,因为当你使用远程的H5 url的时候,打开H5页面的速度由网络决定,而不是由手机性能决定,假如用户在没有网络的地 ...
- springBoot整合Sentinel实现降级限流熔断
由于hystrix的停止更新,以及阿里Sentinel在历年双十一的贡献.项目中使用了Sentinel,今天我们来讲讲Sentinel的入门教程,本文使用1.6.3版本进行讲解 本文通过Sentine ...
- Ubuntu Server 16.04.1 LTS 64位 搭建LNMP环境
安装配置 Nginx 为了确保获得最新的 Nginx,先使用sudo apt-get update命令更新源列表.安装 Nginx,输入命令:sudo apt-get install nginx. 启 ...
- [leetcode]29. Divide Two Integers不用除法实现除法
思路是不断将被除数分为两部分,每次分的一部分都是尽量大的除数的倍数,然后最后的商就是倍数加上剩下的部分再分,知道不够大. 递归实现 剩下的难点就是,正负号(判断商正负后将两个数都取绝对值),数太大(将 ...
- java中装箱,拆箱
(1)包装类 java中不能定义基本数据类型的对象,但是提供了基本数据类型的包装类来解决这一问题,例如int类型的包装类Integer, 包装类的常用方法有Integer.ParseInt()方法类将 ...
- Phoneix(四)hbase导入数据同时与phoenix实现映射同步
一.说明 先创建一个hbase表格,能够导入本地数据到hbase中,最后能够通过phoneix进行访问. 1.数据准备(10W条,样例如下),文件test.txt 0,20190520164020,1 ...
- 七、Elasticsearch+elasticsearch-head的安装+Kibana环境搭建+ik分词器安装
一.安装JDK1.8 二.安装ES 三个节点:master.slave01.slave02 1.这里下载的是elasticsearch-6.3.1.rpm版本包 https://www.elastic ...