前言
在了解child_process之前,我们先来了解几个计算机操作系统中的基本概念,以及他们之间存在的关系。
- cup: 计算机包含五大基本硬件运算器、控制器、存储器、输入,输出设备。运算器和控制器集成为中央处理单元即CPU(Central Processing Unit),其主要作用是执行一系列指令运算然后将结果写回。
- 进程: 进程是系统进行资源分配和调度的基本单位,同一时间在单个CUP上只能有一个进程运行,它会占用独立的内存。但是我们可能会想到,计算机运行的时候肯定不只是只有一个进程在运行。在现代操作系统中,所有进程会轮流使用cpu,但是由于cpu的运行效率极高,可以在多个任务间快速切换,给我们的感觉就好像多个任务在并发执行。
- 线程: 线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,线程共享该进程所拥有的全部资源,但是当有其中一个线程使用某一块共享内存的时候,其他线程必须等待它结束后,才能使用这一块内存。
我们知道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模块正是用来创建子进程的。
child_process提供了几种创建子进程的方式
- 异步方式:
spawn
、exec
、execFile
、fork
- 同步方式:
spawnSync
、execSync
、execFileSync
exec
语法: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
execFile
语法: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
spawn
语法: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
// ...
fork
语法:child_process.fork(modulePath[, args][, options])
fork 其实是 spawn 的一个特殊例子,因为 fork() 第一个参数是一个 node module path。args 和 options 参数都和 spawn 一致。但是 fork 执行的是一个 node module,所以 fork 提供了一个特性,即在父子进程之间建立一个 IPC 通道,使父子进程之间通过 send() 方法来互相发送信息。如下例子:
child.js
//child.js
process.on('message',function(msg){
console.log('child receive msg:', msg) // child receive msg: hello world
process.send(msg)
})
parent.js
// parent.js
let cp=require('child_process');
let child=cp.fork('./child');
child.on('message',function(msg){
console.log('parent get messg:',msg); // parent get messg: hello world
});
child.send('hello world');
因为 fork 会在父子进程间建立通信通道,所以如果有同步的 fork,那么这个 IPC 通道就不会存在,所以 fork 没有对应的同步方法。
总结
四种创建子进程的方法中 spawn 和 fork 要相对常用一些,spawn 处理操作系统命令;fork 处理 node module(并且父子进程间会建立 IPC 通道进行通信);exec 是直接使用 shell 执行命令,所以可以方便使用 shell 中的管道等特性,但是输出结果会在回调中一次输出,所以不适合输出数据特别大的情况;execFile 不适合 Windows 系统。