Прежде чем представить модуль child_process, давайте рассмотрим пример.
const http = require('http');
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e10; 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);
Вы можете попробовать использовать приведенный выше код для запуска службы Node.js, а затем открыть две вкладки браузера для доступа к /compute и / соответственно.Вы можете обнаружить, что когда служба узла получает запрос /compute, она выполняет большое количество числовых расчетов, что приводит к невозможности ответить на другие вопросы (/).
В языке Java вышеуказанные проблемы можно решить с помощью многопоточности, но Node.js является однопоточным при выполнении кода, так как же Node.js должен решать вышеуказанные проблемы? Фактически, Node.js может создать дочерний процесс для выполнения ресурсоемких вычислительных задач процессора (таких как longComputation в приведенном выше примере) для решения проблемы, а модуль child_process используется для создания дочерних процессов.
Как создать дочерний процесс
child_process предоставляет несколько способов создания дочерних процессов.
- Асинхронный режим: spawn, exec, execFile, fork
- Метод синхронизации: spawnSync, execSync, execFileSync
Сначала введите метод spawn
child_process.spawn(command[, args][, options])
command: 要执行的指令
args: 传递参数
options: 配置项
const { spawn } = require('child_process');
const child = spawn('pwd');
pwd это команда оболочки, используемая для получения текущего каталога.После выполнения приведенного выше кода в консоль не выводится информация.Почему так?
Причина, по которой консоль не может видеть выходную информацию, заключается в том, что дочерний процесс имеет свой собственный поток stdio (stdin, stdout, stderr), а вывод консоли привязан к stdio текущего процесса, поэтому, если вы хотите увидеть выходная информация. Этого можно добиться, установив канал между стандартным выводом дочернего процесса и стандартным выводом текущего процесса.
child.stdout.pipe(process.stdout);
Вы также можете прослушивать события (все потоки stdio дочернего процесса реализуют API EventEmitter, поэтому вы можете добавить мониторинг событий)
child.stdout.on('data', function(data) {
process.stdout.write(data);
});
Console.log, используемый в коде Node.js, фактически зависит от process.stdout внизу.
Помимо создания конвейера, это также может быть достигнуто путем совместного использования stdio дочерним процессом и текущим процессом.
const { spawn } = require('child_process');
const child = spawn('pwd', {
stdio: 'inherit'
});
Параметр stdio используется для настройки конвейера, установленного между родительским процессом и дочерним процессом.Поскольку существует три конвейера stdio (stdin, stdout, stderr), три возможных значения stdio фактически являются сокращением для массива
- pipe эквивалентно ['pipe', 'pipe', 'pipe'] (по умолчанию)
- игнорировать эквивалентно ['игнорировать', 'игнорировать', 'игнорировать']
- наследование эквивалентно [process.stdin, process.stdout, process.stderr]
Поскольку метод inherit заставляет дочерний процесс напрямую использовать stdio родительского процесса, вы можете увидеть вывод
ignore используется для игнорирования вывода дочернего процесса (/dev/null указывается как файловый дескриптор дочернего процесса), поэтому child.stdout имеет значение null при игнорировании.
spawn по умолчанию не создает подоболочку для выполнения команд, поэтому следующий код сообщит об ошибке
const { spawn } = require('child_process');
const child = spawn('ls -l');
child.stdout.pipe(process.stdout);
// 报错
events.js:167
throw er; // Unhandled 'error' event
^
Error: spawn ls -l ENOENT
at Process.ChildProcess._handle.onexit (internal/child_process.js:229:19)
at onErrorNT (internal/child_process.js:406:16)
at process._tickCallback (internal/process/next_tick.js:63:19)
at Function.Module.runMain (internal/modules/cjs/loader.js:746:11)
at startup (internal/bootstrap/node.js:238:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)
Emitted 'error' event at:
at Process.ChildProcess._handle.onexit (internal/child_process.js:235:12)
at onErrorNT (internal/child_process.js:406:16)
[... lines matching original stack trace ...]
at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)
Если необходимо передать параметры, они должны быть переданы в виде массива
const { spawn } = require('child_process');
const child = spawn('ls', ['-l']);
child.stdout.pipe(process.stdout);
Если вы хотите выполнитьls -l | wc -l
Команду можно использовать для создания двух команд спавна.
const { spawn } = require('child_process');
const child = spawn('ls', ['-l']);
const child2 = spawn('wc', ['-l']);
child.stdout.pipe(child2.stdin);
child2.stdout.pipe(process.stdout);
также можно использоватьexec
const { exec } = require('child_process');
exec('ls -l | wc -l', function(err, stdout, stderr) {
console.log(stdout);
});
Поскольку exec создает подоболочку, вы можете напрямую выполнять команды конвейера оболочки. Spawn использует поток для вывода результата выполнения команды, а exec также кэширует результат выполнения команды и помещает его в параметры callback-функции, поэтому exec подходит только для случая, когда данные результата выполнения команды маленький.
Фактически, spawn также может создавать подоболочки, настраивая параметры оболочки для поддержки команд конвейера, как показано ниже.
const { spawn, execFile } = require('child_process');
const child = spawn('ls -l | wc -l', {
shell: true
});
child.stdout.pipe(process.stdout);
В дополнение к stdio и оболочке элементы конфигурации также включают общие параметры, такие как cwd, env и detached.
cwd используется для изменения каталога выполнения команды
const { spawn, execFile, fork } = require('child_process');
const child = spawn('ls -l | wc -l', {
shell: true,
cwd: '/usr'
});
child.stdout.pipe(process.stdout);
env используется для указания переменных среды дочернего процесса (если не указано, переменные среды текущего процесса получаются по умолчанию)
const { spawn, execFile, fork } = require('child_process');
const child = spawn('echo $NODE_ENV', {
shell: true,
cwd: '/usr'
});
child.stdout.pipe(process.stdout);
NODE_ENV=randal node b.js
// 输出结果
randal
Если указан env, переменные среды по умолчанию будут перезаписаны следующим образом.
const { spawn, execFile, fork } = require('child_process');
spawn('echo $NODE_TEST $NODE_ENV', {
shell: true,
stdio: 'inherit',
cwd: '/usr',
env: {
NODE_TEST: 'randal-env'
}
});
NODE_ENV=randal node b.js
// 输出结果
randal-env
detached используется для отключения дочернего процесса от родительского процесса
Например, предположим, что есть длительный дочерний процесс.
// timer.js
while(true) {
}
Но если основной процесс не нуждается в длительном запуске, можно использовать detached, чтобы разорвать соединение между двумя
const { spawn, execFile, fork } = require('child_process');
const child = spawn('node', ['timer.js'], {
detached: true,
stdio: 'ignore'
});
child.unref();
Когда вызывается метод unref дочернего процесса, а stdio дочернего процесса настроен как игнорирование, родительский процесс может завершиться независимо.
execFile отличается от exec, execFile обычно используется для выполнения файлов и не создает среду подоболочки
Метод fork является частным случаем метода spawn.Fork используется для выполнения файла js для создания дочернего процесса Node.js. Кроме того, между дочерним процессом, созданным fork, и родительским процессом устанавливается конвейер связи IPC, поэтому сообщения могут отправляться между дочерним процессом и родительским процессом посредством отправки.
Примечание: дочерний процесс, созданный fork, полностью независим от родительского процесса, имеет отдельную память и отдельный экземпляр V8, поэтому не рекомендуется создавать много дочерних процессов Node.js.
Для связи между родительским и дочерним процессами в режиме fork обратитесь к следующему примеру.
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);
node parent.js
// 输出结果
Message from parent: { hello: 'world' }
Message from child { counter: 0 }
Message from child { counter: 1 }
Message from child { counter: 2 }
Message from child { counter: 3 }
Message from child { counter: 4 }
Message from child { counter: 5 }
Message from child { counter: 6 }
Возвращаясь к вопросу в начале этой статьи, мы можем вынести логику интенсивного расчета в отдельный файл js, а затем вычислить его форком, а затем оповестить основной процесс о результате расчета, когда расчет будет завершен, поэтому чтобы избежать занятости основного процесса.
compute.js
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e10; i++) {
sum += i;
};
return sum;
};
process.on('message', (msg) => {
const sum = longComputation();
process.send(sum);
});
index.js
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);
Отслеживание событий процесса
Все дочерние процессы, созданные указанными выше способами, реализуют EventEmitter, поэтому события для процесса можно отслеживать.
Обычно используемые события включают несколько: закрытие, выход, ошибка, сообщение.
Событие close запускается при закрытии потока stdio дочернего процесса, а не при завершении дочернего процесса, поскольку несколько дочерних процессов могут совместно использовать один и тот же stdio.
Функция обратного вызова событий закрытия и выхода имеет два параметра: код и сигнал. Код кода является окончательным кодом выхода дочернего процесса. Если дочерний процесс завершается из-за получения сигнала сигнала, сигнал запишет принятое значение сигнала. дочерним процессом.
Давайте рассмотрим пример нормального выхода
const { spawn, exec, execFile, fork } = require('child_process');
const child = exec('ls -l', {
timeout: 300
});
child.on('exit', function(code, signal) {
console.log(code);
console.log(signal);
});
// 输出结果
0
null
Давайте посмотрим на другой пример, который завершается из-за получения сигнала.Файл таймера перед использованием приложения, и время ожидания указывается при использовании exec для выполнения.
const { spawn, exec, execFile, fork } = require('child_process');
const child = exec('node timer.js', {
timeout: 300
});
child.on('exit', function(code, signal) {
console.log(code);
console.log(signal);
});
// 输出结果
null
SIGTERM
Примечание. Событие ошибки не будет инициировано по истечении времени ожидания, и событие выхода не обязательно будет инициировано при срабатывании события ошибки.
Условия срабатывания события ошибки следующие:
- Не удалось создать процесс
- Не удалось завершить процесс
- Не удалось отправить сообщение процессу
Обратите внимание, что при возникновении ошибки в выполнении кода событие ошибки не срабатывает, срабатывает событие выхода, а код является кодом выхода исключения, отличным от 0.
const { spawn, exec, execFile, fork } = require('child_process');
const child = exec('ls -l /usrs');
child.on('error', function(code, signal) {
console.log(code);
console.log(signal);
});
child.on('exit', function(code, signal) {
console.log('exit');
console.log(code);
console.log(signal);
});
// 输出结果
exit
1
null
Событие сообщения подходит для передачи информации, когда между родительским и дочерним процессами установлен конвейер связи IPC.Процесс передачи будет проходить этапы сериализации и десериализации, поэтому окончательные полученные данные могут не соответствовать отправленным данным.
sub.js
process.send({ foo: 'bar', baz: NaN });
const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);
n.on('message', (m) => {
console.log('got message:', m); // got message: { foo: 'bar', baz: null }
});
Следует отметить особый случай сообщения: следующее сообщение не будет получено дочерним процессом.
const { fork } = require('child_process');
const forked = fork('child.js');
forked.send({
cmd: "NODE_foo",
hello: 'world'
});
Когда отправленное сообщение содержит атрибут cmd, а значение атрибута равноNODE_
В начале такое сообщение зарезервировано для самого Node.js, поэтому выдаваться не будетmessage
событие, но будет испускатьinternalMessage
событий, разработчикам следует избегать сообщений такого типа и избегать прослушиванияinternalMessage
мероприятие.
Помимо отправки строк и объектов, сообщение также поддерживает отправку серверных объектов и объектов сокетов.Благодаря поддержке объектов сокетов несколько процессов Node.js могут прослушивать один и тот же номер порта.
Продолжение следует...