【问题标题】:when does Node spawned child process actually start?Node 生成的子进程何时真正开始?
【发布时间】:2020-05-04 23:43:09
【问题描述】:

documentation for Node's Child Process spawn() function 和我在其他地方看到的示例中,模式是调用spawn() 函数,然后然后 在返回的@987654325 上设置一堆处理程序@ 目的。例如,这是该文档页面上给出的spawn() 的第一个示例:

const { spawn } = require('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() 函数本身在第二行被调用。我的理解是spawn() 异步启动一个子进程。来自文档:

child_process.spawn() 方法使用给定的方法生成一个新进程 命令,在 args 中带有命令行参数。

但是,上面脚本的以下几行继续为进程设置各种处理程序,因此假设在第 2 行调用 spawn() 之间该进程尚未实际启动(并且可能完成)其他事情发生在后续行。我知道 JavaScript/Node 是单线程的。但是,操作系统不是单线程的,并且天真地认为spawn() 调用告诉操作系统立即生成进程(此时,不幸的时机,操作系统可能会暂停父节点进程并在执行下一行 Node 代码之前运行/完成子进程)。

但必须是在当前 JavaScript 函数完成(或更一般地说,调用当前函数的当前 JavaScript 事件处理程序完成)之前,该进程实际上并没有生成,对吧?

这似乎是一件很重要的事情。为什么在子进程文档页面中没有这样说?是否有一些压倒一切的 Node 原则无需明确说明?

【问题讨论】:

标签: node.js asynchronous spawn


【解决方案1】:

新进程的生成立即开始(它被移交给操作系统以实际启动进程并使其运行)。使用.spawn() 启动新进程是异步且非阻塞的。因此,它将启动与操作系统的操作并立即返回。您可能认为这就是为什么在它返回后设置事件处理程序是可以的(因为该过程尚未完成启动)。嗯,是的,也不是。它可能还没有完成启动新流程,但这不是它正常的主要原因。

没关系,因为 node.js 通过一个单线程事件队列运行它的所有事件。因此,在您的代码完成执行并将控制权返回给系统之前,无法处理来自新生成的进程的事件。只有这样它才能处理事件队列中的下一个事件并触发您为其注册处理程序的事件之一。

或者,换一种说法,来自其他进程的事件都不是先发制人的。他们不会/不能中断您现有的 Javascript 代码。因此,由于您仍在运行 Javascript 代码,因此这些事件还无法运行。相反,它们位于事件队列中,直到您的 Javascript 代码完成,然后解释器可以从事件队列中获取下一个事件并运行与之关联的回调。同样,该回调一直运行,直到它返回给解释器,然后解释器可以获取下一个事件并运行它的回调等等......

这就是为什么 node.js 被称为事件驱动系统。

因此,做这种类型的结构非常好:

const { spawn } = require('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}`);
});

在您的代码完成并将控制权返回给系统之前,这些dataclose 事件都不能执行它们的回调。因此,像您一样设置这些事件处理程序是非常安全的。即使新生成的进程正在运行并立即生成事件,这些事件也只会留在事件队列中,直到您的 Javascript 完成它正在执行的操作(包括设置您的事件处理程序)。

现在,如果您延迟设置事件处理程序,直到事件循环的某个未来滴答声(如下所示)使用 setTimeout() 之类的东西,那么您可能会错过一些事件:

const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

setTimeout(() => {    
    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}`);
    });
}, 10);

在这里,您不会立即将事件处理程序设置为事件循环的同一滴答声的一部分,而是在短暂的延迟之后。因此,在您安装事件处理程序之前,可能会从事件循环中处理一些事件,您可能会错过其中的一些事件。显然,您永远不会这样做(故意),但我只是想表明在事件循环的同一滴答声上运行的代码没有问题,但在事件循环的某个未来滴答声上运行的代码可能有缺少事件的问题。

【讨论】:

  • 感谢您的解释。我将添加自己的答案以跟进您的答案,解释您的答案帮助我理解的具体内容。
【解决方案2】:

这是对jfriend00的回答的跟进,解释它帮助我理解了什么,以防它帮助别人。我知道 JavaScript/Node 的事件驱动特性。 jfriend00 的解释让我清楚的是,一个事件可能发生并且 Node 可以知道它发生了,但它实际上并没有决定在下一个滴答之前告诉哪些处理程序。例如,如果 spawn() 调用完全失败(例如,命令不存在),Node 显然会立即知道这一点。我的想法是,它会立即将适当的处理程序排队,以便在下一个滴答时运行。但是我现在理解的是,它将“原始事件”(即生成失败的事实,以及有关该事件的任何细节)放入其队列中,然后在下一个滴答声中 确定 和调用适当的处理程序。对于其他事件也是如此,例如从进程接收输出等。事件被保存,但事件的适当处理程序仅在下一个滴答运行时确定,因此在前一个滴答上分配的处理程序,在 spawn() 之后,会被调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-14
    • 1970-01-01
    • 2019-01-26
    • 1970-01-01
    • 2013-12-31
    • 1970-01-01
    相关资源
    最近更新 更多