Возможность появления условия гонки child_process в nodejs

Я начинаю изучать и использовать узел, и он мне нравится, но я не совсем уверен, как работают определенные функции. Может быть, вы можете помочь мне решить одну такую ​​​​проблему:

Я хочу создавать локальные скрипты и программы с моего сервера узлов по командам отдыха. Глядя на библиотеку fs, я увидел приведенный ниже пример того, как создать дочерний процесс и добавить к нему несколько обработчиков каналов/событий.

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);
  }
});

Что странно для меня, так это то, что я не понимаю, как я могу гарантировать, что код обработчика событий будет зарегистрирован до того, как программа начнет работать. Это не похоже на функцию «возобновление», которую вы запускаете, чтобы запустить ребенка. Разве это не состояние гонки? Конечно, условие будет крошечным и почти никогда не сработает, потому что это такой короткий фрагмент кода после этого, но все же, если это так, я бы предпочел не кодировать его таким образом из хороших привычек.

Итак: 1) если это не состояние гонки, то почему? 2) если это состояние гонки, как я могу написать это правильно?

Спасибо за ваше время!


person ThinkBonobo    schedule 17.02.2015    source источник


Ответы (2)


Это не состояние гонки. Node.js является однопоточным и обрабатывает события в порядке очереди. Новые события помещаются в конец цикла событий. Node будет выполнять ваш код синхронно, часть которого будет включать в себя настройку эмиттеров событий. Когда эти эмиттеры событий генерируют события, они помещаются в конец очереди и не будут обрабатываться до тех пор, пока Node не завершит выполнение любого фрагмента кода, над которым он работает в данный момент, что оказывается тем же кодом, который регистрирует прослушиватель. Поэтому прослушиватель всегда будет зарегистрирован до обработки события.

person Yuri Zarubin    schedule 17.02.2015
comment
Спасибо, Юрий, это имеет смысл для ситуации, которую я спросил, где делается спавн из основного кода сервера. Просто чтобы получить больше разъяснений, если бы я породил ребенка, который, в свою очередь, породил детей, ваша логика осталась бы в силе? - person ThinkBonobo; 18.02.2015
comment
Без проблем. Не понимаю, почему бы и нет. - person Yuri Zarubin; 18.02.2015
comment
Я этого не понимаю. Node может управлять только своей собственной потоковой моделью, он не имеет контроля над дочерними процессами, которые он порождает. Когда я просматриваю некоторый код, который spawn является дочерним процессом, и проверяю возвращаемое значение spawn, он уже имеет идентификатор процесса, как только я достигаю следующего оператора, что означает, что процесс выполняется до того, как любой из моих обработчиков был зарегистрирован (по крайней мере в Windows). Это означает, что вы ошибаетесь. - person Asad Saeeduddin; 13.07.2017
comment
Да, ваш дочерний процесс, безусловно, запущен и, возможно, генерирует события, но эти события еще не обрабатываются. Используя пример OP, только после завершения выполнения текущего контекста (в котором порождается дочерний процесс и настраиваются обработчики событий) Node начинает обрабатывать события, испускаемые дочерним процессом. - person Yuri Zarubin; 15.07.2017

Учитывая небольшой конфликт и двусмысленность в комментариях к принятому ответу, приведенный ниже образец и вывод говорят мне две вещи:

  1. Дочерний процесс (со ссылкой на объект узла, возвращаемый spawn) не генерирует никаких событий, даже если реальный базовый процесс находится в режиме реального времени/выполняется.
  2. Каналы для IPC настраиваются до выполнения дочернего процесса.

Оба очевидны. Конфликт w.r.t. интерпретация вопроса ОП: -

На самом деле «да», это воплощение состояния гонки данных, если нужно учитывать побочные эффекты реального дочернего процесса. Но «нет», никакой гонки данных в том, что касается сантехники IPC, нет. Данные записываются в буфер и извлекаются в виде (большего) большого двоичного объекта по мере (как уже хорошо описано) завершения контекста, позволяющего продолжить цикл обработки событий.

Первое событие данных, показанное ниже, выталкивает не 1, а 5 фрагментов, записанных в стандартный вывод дочерним процессом, пока мы блокируем... таким образом, ничего не потеряно.

образец:

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
person elbeardmorez    schedule 09.05.2019