когда на самом деле запускается дочерний процесс Node? - PullRequest
0 голосов
/ 18 января 2020

В документации для дочернего процесса Node spawn() функция и в примерах, которые я видел в других местах, шаблон должен вызывать функцию spawn(), а затем для установить несколько обработчиков для возвращенного объекта ChildProcess. Например, вот первый пример 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 () порождает новый процесс, используя данную команду, с аргументами командной строки в аргументах.

Однако следующие строки приведенного выше сценария go для установки различных обработчиков для процесса, поэтому предполагается, что процесс фактически не запущен (и, возможно, не завершен) между временем вызова spawn() в строке 2 и другими событиями, происходящими в последующие строки. Я знаю, что JavaScript / Node однопоточный. Тем не менее, операционная система не является однопоточной, и наивно можно было бы прочитать, что spawn() вызовет указание операционной системе прямо сейчас запускать процесс (в этот момент, при неудачной синхронизации, ОС может приостановить процесс родительского узла и запустить / завершить дочерний процесс до того, как будет выполнена следующая строка кода узла).

Но должно быть так, что процесс фактически не запускается до тех пор, пока текущая функция JavaScript не завершится (или, в более общем случае, обработчик события current JavaScript, который вызвал текущую функцию, завершается), верно?

Это кажется довольно важной вещью. Почему это не сказано на странице документации дочернего процесса? Есть ли какой-то главный принцип Node, который делает ненужным говорить это явно?

Ответы [ 2 ]

1 голос
/ 18 января 2020

Порождение нового процесса начинается немедленно (он передается ОС, чтобы фактически запустить процесс и запустить его). Запуск нового процесса с .spawn() является асинхронным и неблокирующим. Итак, он начнет работу с ОС и сразу же вернется. Вы можете подумать, что именно поэтому все в порядке, чтобы установить обработчики событий после его возвращения (потому что процесс еще не закончил запуск). Ну да и нет. Вероятно, он еще не закончил запуск нового процесса, но это не главная причина, по которой все в порядке.

Все нормально, потому что node.js пропускает все свои события через одну многопоточную очередь событий. Таким образом, никакие события из недавно созданного процесса не могут быть обработаны, пока ваш код не завершит выполнение и не вернет управление обратно в систему. Только тогда он сможет обработать следующее событие в очереди событий и вызвать одно из событий, для которых вы регистрируете обработчики.

Или, иначе говоря, ни одно из событий другого процесса не имеет преимущественного значения. Они не будут / не могут прервать ваш существующий код Javascript. Итак, поскольку вы все еще запускаете свой код Javascript, эти события еще не могут быть запущены. Вместо этого они сидят в очереди событий до тех пор, пока ваш код Javascript не завершится, и тогда интерпретатор может go получить следующее событие из очереди событий и запустить связанный с ним обратный вызов. Аналогично, этот обратный вызов выполняется до тех пор, пока он не возвращается обратно к интерпретатору, а затем интерпретатор может получить следующее событие и выполнить свой обратный вызов и т. Д. ...

Именно поэтому 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}`);
});

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

Теперь , если вы отложили настройку обработчиков событий до некоторого будущего тика события l oop (как показано ниже) с чем-то вроде 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);

Здесь вы устанавливаете обработчики событий не сразу, как часть одного и того же тика события l oop, но после небольшой задержки. Поэтому некоторые события могут быть обработаны из события l oop перед установкой обработчиков событий, и вы можете пропустить некоторые из этих событий. Очевидно, что вы никогда бы не сделали это таким образом (намеренно), но я просто хотел показать, что код, работающий на одном и том же тике события l oop, не имеет проблем, но код, запущенный на некотором будущем тике события У l oop может быть проблема с пропущенными событиями.

0 голосов
/ 18 января 2020

Это продолжение ответа jfriend00, объяснение того, что мне помогло понять, на случай, если это поможет кому-то еще. Я знал об управляемой событиями природе JavaScript / Node. Что объяснение jfriend00 прояснило мне, так это идея о том, что событие может произойти, и Node может знать, что это произошло, но на самом деле он не решает, какие обработчики сообщить об этом событии до следующей отметки. Например, если вызов spawn() завершается неудачно (например, команда не существует), Node, очевидно, знает об этом немедленно. Я думал, что он немедленно поставит в очередь соответствующие обработчики для запуска на следующем тике. Но теперь я понимаю, что он помещает «необработанное событие» (т. Е. Тот факт, что спавн не удался, с какими-либо подробностями об этом) в свою очередь, а затем на следующем тике оно определяет и вызывает соответствующие обработчики. То же самое верно и для других событий, таких как получение выходных данных процесса и т. Д. c. Событие сохраняется, но соответствующие обработчики для события определяются только при запуске следующего тика, поэтому будут вызываться обработчики, назначенные на предыдущем тике после spawn ().

...