},
上面代码中的echo命令无法访问父进程的环境变量。例如,它无法访问$HOME,但是它可以访问$ANSWER,因为我们将$ANSWER通过env选项指定为自定义的环境变量。
最后一个重要的选项是detached,它使子进程独立于父进程运行。
假设我们有一个timer.js文件,它在事件循环中保持运行:
setTimeout(() => {
// keep the event loop busy
}, 20000);
我们可以使用detached选项让它在后台运行:
const { spawn } = require('child_process');
const child = spawn('node', ['timer.js'], {
detached: true,
stdio: 'ignore'
child.unref();
从父进程分离子进程的具体行为取决于操作系统。在Windows中,分离的子进程具有自己独立的的控制台窗口,而在Linux中,分离的子进程会创建一个新的进程组和会话标签。
如果在分离的子进程上调用了unref函数,则父进程可以独立于子进程退出。如果子进程正在执行一个时间比较长的任务,这个功能会很有用。要让子进程在后台保持运行,我们还需要将子进程的stdio选项也配置为独立于父进程。
上面的示例通过分离的子进程在后台执行一个Node脚本(timer.js),并且忽略了父进程的stdio文件描述符,这样当父进程被终止时,子进程仍然可以在后台保持运行。
execFile函数
如果你想在不使用shell的情况下执行一个文件,可以使用execFile函数。它与exec函数的行为完全相同,只是不使用shell,这使得execFile函数的执行效率更高。在Windows中,某些文件无法单独执行,例如.bat或.cmd文件。这些文件不能使用execFile函数执行,不过可以使用exec或spawn函数并将shell选项设置为true来执行它们。
*Sync函数
child_process模块中的spawn,exec和execFile函数都有对应的同步版本,当调用这些函数时,它会阻塞当前程序的执行直到子进程退出才会继续下一步。
const {
spawnSync,
execSync,
execFileSync,
} = require('child_process');
如果你试图简化脚本编写或者执行任何脚本任务,这些同步版本可能会很有用,但应该尽量避免使用它们。
fork()函数
fork函数是spawn函数的变体,它用来产生一个node进程。spawn和fork之间的最大区别在于,当使用fork时,子进程的通信信道会被建立,因此我们可以在子进程中使用send函数与全局对象process一起在父进程和子进程之间交换信息。我们通过EventEmitter模块接口来实现这一操作。下面是具体的例子:
文件parent.js:
const { fork } = require('child_process');
const forked = fork('child.js');
forked.on('message', (msg) => {
console.log('Message from child', msg);
forked.send({ hello: 'world' });
文件child.js:
process.on('message', (msg) => {
console.log('Message from parent:', msg);
let counter = 0;
setInterval(() => {
process.send({ counter: counter++ });
}, 1000);
在上面的parent.js文件中,我们fork了child.js(这将使用node命令执行该文件),然后监听message事件。每当child.js使用process.send发送数据时,message事件都会被触发。在child.js中,每隔一秒都会调用process.send方法。
要将消息从parent传递给child,我们可以在对象forked上执行send函数,然后在child.js中,我们监听全局对象process的message事件。
当执行上面程序中的parent.js文件时,它首先向下发送{ hello: 'world' }对象,forked的子进程打印一个消息(Message from parent: { hello: 'world' }),然后child.js将每秒发送一个递增的数值给父进程打印消息(Message from child{ counter: 0 })。
下面让我们来一个有关fork函数的更实际的例子。
假设我们有一个http服务器,用来处理两个endpoint。其中一个endpoint(/compute)耗时较长,它需要几秒钟才能响应。我们可以使用一个长的for循环来模拟它:
const http = require('http');
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e9; 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);
这个程序有一个很大的问题。当请求/compute时,由于事件循环忙于处理那个长的for循环操作,因此服务器将无法处理其它的请求。
有几种方法可以解决此问题,不过有一种解决办法适用于所有的操作,我们可以使用fork将计算移至另一个进程中。
首先,我们将整个longComputation函数移至一个新的文件中,并通过主进程的消息来调用该函数:
文件computer.js:
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
return sum;
process.on('message', (msg) => {
const sum = longComputation();
process.send(sum);
现在,我们不需要在主进程的事件循环中进行一个很费时的操作,我们可以fork文件compute.js,并使用message接口在服务器和fork的进程之间传递消息。
const http = require('http');
const { fork } = require('child_process');
const server = http.createServer();
server.on('request', (req, res) => {
if (req.url === '/compute') {
const compute = fork('compute.js');
compute.send('start');
compute.on('message', sum => {
res.end(`Sum is ${sum}`);
} else {
res.end('Ok')
server.listen(3000);
当使用上述代码请求/compute时,我们只需要简单地向fork的进程发送一条消息即可执行那个很费时的操作,而主进程的事件循环不会被阻塞。
一旦fork的进程完成了那个很费时的操作,它可以通过process.send将结果发送给主进程。
在父进程中,我们监听fork的进程的message事件。当该事件被触发后,我们获取到sum值,然后通过http返回给请求者。
当然,上面的代码中,我们可以fork的进程的数量是受限制的,但是当我们执行它并通过http请求一个耗时较长的endpoint时,主服务器不会被阻塞从而可以继续响应其它的请求。
原文地址:Node.js Child Processes: Everything you need to know