在了解child_process之前,我们先来了解几个计算机操作系统中的基本概念,以及他们之间存在的关系。
我们知道node是单线程运行的,当我们用node app.js启动node服务的时候会在服务器上运行一个node的进程,我们的js代码只会在其中的一个线程运行。在node的设计中就是将耗时长的操作代理给操作系统或者其他线程,这部分操作就是磁盘I/O和网络I/O
等常见的异步操作,并且将这些耗时的操作从主线程上脱离。虽然node从语言层面不支持创建线程,但是我们可以通过child_process模块创建一个新的进程完成耗时耗费资源的操作,比如说要执行一段上传或下载大文件的shell脚本,然后将执行结果回传给主线程。
本文主要给大家介绍了关于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模块正是用来创建子进程的。
spawn
、exec
、execFile
、fork
spawnSync
、execSync
、execFileSync
语法:child_process.exec(command[, options][, callback])
这里的第一个参数 command 就是在 shell 中执行的命令;options 可以设置与执行命令相关的参数,如:cwd(当前工作目录)、shell(执行命令的shell)、uid、gid、encoding等;callback 在命令执行完调用,可通过回调函数的 stdout 获取命令输出。options 和 callback 都是可选参数。比如想在 /Usrs/ben 目录下执行 “ls -l”,那么代码如下:
const { exec } = require('child_process');exec('ls -l',{cwd: '/Users/liu/Desktop'}, (error, stdout, stderr) => {if (error) return;console.log('stdout:', stdout);
})// stdout: total 4792
// -rw-r--r--@ 1 liuchongyang staff 53248 May 24 2022 10起诉状(一审).doc
// -rw-r--r--@ 1 liuchongyang staff 34816 May 24 2022 11答辩状(一审).doc
// -rw-r--r--@ 1 liuchongyang staff 33280 May 24 2022 12质证意见(一审).doc
// -rw-r--r--@ 1 liuchongyang staff 65024 May 24 2022 13代理词(一审营).doc
语法:child_process.execFile(file[, args][, options][, callback])
execFile 顾名思义就是执行可执行的文件。通常在 Unix 类型的操作系统中,execFile 相比 exec 执行会更加高效,因为其不产生 shell。在 Windows 系统中,由于 .bat
和 .cmd
文件在没有终端的情况下不能单独执行的,所以不能使用 execFile 来执行,而应该使用 exec 或下面介绍的 spawn 来执行。
execFile 方法中 file 参数是必传,指定要执行的文件;args 可选,是给执行文件传的参数列表;options 和 callback 和 exec中的类似,就不细说了。下面,我们想看一下 node 版本,代码可如下:
const { execFile } = require('child_process');execFile('/usr/local/bin/node', ['-v'], (error, stdout, stderr) => {if (error) return;console.log('stdout:', stdout);
})// stdout: v12.17.0
语法:child_process.spawn(command[, args][, options])
spawn
和exec
特性类似,都是执行一个命令,但是 spawn 并没有以回调函数的形式来接收 stdout,而是通过子进程对象上 stdout 监听 data 事件来获取标准输出数据。这样的方式使 stdout 以流的形式传输,相比 exec 要等输出结束之后才会调用回调的方式,要高效很多。
我们用 exec 中举的例子,在 /Usrs/ben 目录下执行 “ls -l”,那么代码如下:
const { spawn } = require('child_process');const subprocess = spawn('ls', ['-l'], {cwd: '/Users/ben'});subprocess.stdout.on('data', (data) => {console.log(data.toString());
});// total 8
// drwx------@ 7 ben staff 224 7 28 09:36 Applications
// drwx------@ 25 ben staff 800 7 31 22:19 Desktop
// drwx------@ 24 ben staff 768 6 21 17:18 Documents
// drwx------@ 79 ben staff 2528 7 31 20:25 Downloads
// ...
语法:child_process.fork(modulePath[, args][, options])
fork 其实是 spawn 的一个特殊例子,因为 fork() 第一个参数是一个 node module path。args 和 options 参数都和 spawn 一致。但是 fork 执行的是一个 node module,所以 fork 提供了一个特性,即在父子进程之间建立一个 IPC 通道,使父子进程之间通过 send() 方法来互相发送信息。如下例子:
// parent
//通过process.on('message')和process.send()的机制来接收和发送消息//child.js
process.on('message',function(msg){process.send(msg)
})// parent.js
let cp=require('child_process');
let child=cp.fork('./child');
child.on('message',function(msg){console.log('got a message is',msg);
});
child.send('hello world');// child.disconnect() 父进程中调用, 断开父子间IPC通信// got a message is hello world
因为 fork 会在父子进程间建立通信通道,所以如果有同步的 fork,那么这个 IPC 通道就不会存在,所以 fork 没有对应的同步方法。
四种创建子进程的方法中 spawn 和 fork 要相对常用一些,spawn 处理操作系统命令;fork 处理 node module(并且父子进程间会建立 IPC 通道进行通信);exec 是直接使用 shell 执行命令,所以可以方便使用 shell 中的管道等特性,但是输出结果会在回调中一次输出,所以不适合输出数据特别大的情况;execFile 不适合 Windows 系统。