进程的概念
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。
拿我们在电脑中打开webstorm并且运行一个cli项目来说:
OS是指操作系统,那么launchd就是桌面进程,我们在桌面点击启动webstorm就创建了webstorm的进程,此时launchd就是webstorm的父进程,然后在webstorm调用node命令执行一个项目,就又启动了一个执行该项目的进程,在这个项目里面,我们又可以通过child_process API启动很多项目的子进程。
child_process API
exec和execFile
两者的函数签名如下:
child_process.exec(command[, options][, callback]);
child_process.execFile(file[, args][, options][, callback]);
exec主要用来执行shell命令;而execFile主要用来执行可执行文件,其第二个参数为执行文件执行时注入的参数。其实exec一样可以用来执行可执行文件,不过使用exec时没法注入参数。
spawn
spawn的函数签名如下:
child_process.spawn(command[, args][, options])
入参是命令、参数列表、options,可以看到spawn是没有回调函数的,我们在使用的时候通过它的返回值监听data的方式来处理结果:
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
值得注意的是,spawn的第一个参数并不能像exec那样用空格隔开当做命令,spawn需要配合第二个参数来使用,比如使用spawn执行npm install时需要这样写:
spawn('npm', ['install'])
// 下面写法是不会执行的
spawn('npm install')
fork
先看fork的函数签名:
child_process.fork(modulePath[, args][, options])
fork与上述三个方法的不同之处在于使用fork时会再开启一个进程去执行modulePath文件,我们可以使用send去进行两个进程之间的通信:
在父脚本中:
const cp = require('node:child_process');
const n = cp.fork(`${__dirname}/sub.js`);
n.on('message', (m) => {
console.log('PARENT got message:', m);
});
// Causes the child to print: CHILD got message: { hello: 'world' }
n.send({ hello: 'world' });
然后子脚本 'sub.js'
中:
process.on('message', (m) => {
console.log('CHILD got message:', m);
});
// Causes the parent to print: PARENT got message: { foo: 'bar', baz: null }
process.send({ foo: 'bar', baz: NaN });
如何选择
- exec和execFile结果在回调里处理,意味着要等任务执行完成,所以它们适合用在开销比较小的任务。
- spawn则适合用在比较耗时或者需要不断打印日志的任务,比如执行npm install命令
- fork适用于耗时操作,它可以开启额外的进程去执行耗时操作,并且能与主进程进行通信。
同步API:execFileSync、execSync、spawnSync
这些API与上述异步API的使用方法和作用都相同,区别是这些API是同步执行,会阻塞代码执行。