child_process 在 nodejs 中产生竞争条件的可能性

child_process spawn Race condition possibility in nodejs

我开始学习和使用 node,我喜欢它,但我不太确定某些功能是如何工作的。也许你可以帮我解决这样一个问题:

我想根据 rest 命令从我的节点服务器生成本地脚本和程序。查看 fs 库,我看到了下面的示例,说明如何生成 child 进程并在其上添加一些 pipes/event 处理程序。

var spawn = require('child_process').spawn,
    ps    = spawn('ps', ['ax']),
    grep  = spawn('grep', ['ssh']);

ps.stdout.on('data', function (data) {
  grep.stdin.write(data);
});

ps.stderr.on('data', function (data) {
  console.log('ps stderr: ' + data);
});

ps.on('close', function (code) {
  if (code !== 0) {
    console.log('ps process exited with code ' + code);
  }
  grep.stdin.end();
});

grep.stdout.on('data', function (data) {
  console.log('' + data);
});

grep.stderr.on('data', function (data) {
  console.log('grep stderr: ' + data);
});

grep.on('close', function (code) {
  if (code !== 0) {
    console.log('grep process exited with code ' + code);
  }
});

让我感到奇怪的是,我不明白我怎么能保证在程序开始之前注册事件处理程序代码运行。这不像有一个 'resume' 函数,你 运行 启动 child。这不是竞争条件吗?当然条件会非常小并且几乎永远不会命中,因为它之后的代码片段如此短,但如果是这样,我宁愿不以这种方式编写代码,这是出于良好的习惯。

所以: 1)如果不是竞争条件,为什么? 2) 如果它是一个竞争条件,我怎么能以正确的方式写它?

感谢您的宝贵时间!

这不是竞争条件。 Node.js 是单线程的,按照先到先得的原则处理事件。新事件放在事件循环的末尾。 Node 将以同步方式执行您的代码,其中一部分将涉及设置事件发射器。当这些事件发射器发出事件时,它们将被放在队列的末尾,并且在 Node 完成执行其当前正在处理的任何代码之前不会被处理,这恰好与注册侦听器的代码相同。因此,监听器总是会在事件被处理之前注册。

鉴于已接受答案的评论中存在轻微的冲突和歧义,下面的示例和输出告诉我两件事:

  1. 子进程(指的是 spawn 返回的节点对象)不发出任何事件,即使真正的底层进程处于活动/执行状态。
  2. IPC 的管道在执行子进程之前设置。

两者都很明显。冲突是w.r.t。 OP问题的解释:-

实际上'yes',如果需要考虑真正的子进程的副作用,这就是数据竞争条件的缩影。但是 'no',就 IPC 管道管道而言,不存在数据竞争。数据被写入缓冲区并作为(更大的)blob 检索(如前所述)上下文完成允许事件循环继续。

下面看到的第一个数据事件不是将 1 个而是 5 个块推送到我们阻塞时由子进程写入 stdout.. 因此没有任何丢失。

示例:

let t = () => (new Date()).toTimeString().split(' ')[0]
let p = new Promise(function (resolve, reject) {
  console.log(`[${t()}|info] spawning`);

  let cp = spawn('bash', ['-c', 'for x in `seq 1 1 10`; do printf "$x\n"; sleep 1; done']);
  let resolved = false;

  if (cp === undefined)
    reject();

  cp.on('error', (err) => {
    console.log(`error: ${err}`);
    reject(err);
  });

  cp.stdout.on('data', (data) => {
    if (!resolved) {
      console.log(`[${t()}|info] spawn succeeded`);
      resolved = true;
      resolve();
    }
    process.stdout.write(`[${t()}|data] ${data}`);
  });

  let ts = parseInt(Date.now() / 1000);
  while (parseInt(Date.now() / 1000) - ts < 5) {
    // waste some cycles in the current context
    ts--; ts++;
  }

  console.log(`[${t()}|info] synchronous time wasted`);
});
Promise.resolve(p);

输出:

[18:54:18|info] spawning
[18:54:23|info] synchronous time wasted
[18:54:23|info] spawn succeeded
[18:54:23|data] 1
2
3
4
5
[18:54:23|data] 6
[18:54:24|data] 7
[18:54:25|data] 8
[18:54:26|data] 9
[18:54:27|data] 10