Автор выбрал COVID-19 Relief Fund для получения пожертвования в рамках программы Write for DOnations.
Когда пользователь выполняет одну программу Node.js, она работает как один процесс операционной системы (ОС), который представляет экземпляр запущенной программы. В рамках этого процесса Node.js выполняет программы на одном потоке. Как уже упоминалось в этой серии в обучающем руководстве Написание асинхронного кода в Node.js, поскольку только один поток может выполняться в одном процессе, операции, выполнение которых занимает много времени в JavaScript, могут блокировать поток Node.js и задерживать выполнение другого кода. Ключевая стратегия работы над этой проблемой заключается в запуске дочернего процесса или процесса, созданного другим процессом, когда встречаются задачи с длительным выполнением. При запуске нового процесса операционная система может использовать многопроцессорные техники для обеспечения параллельного или одновременного выполнения основного процесса Node.js и дополнительного дочернего процесса.
Node.js включает модуль child_process
, который имеет функции для создания новых процессов. Помимо работы с длительными задачами, этот модуль может также взаимодействовать с ОС и запускать команды оболочки. Системные администраторы могут использовать Node.js для запуска команд оболочки для структурирования и поддержания их операций в качестве модуля Node.js вместо скриптов оболочки.
В этом обучающем руководстве вы создадите дочерние процессы, выполняя серию образцов приложений Node.js. Вы создадите процессы с помощью модуля child_process
путем получения результатов дочернего процесса через буфер или строку с функцией exec()
, а затем из потока данных с функцией spawn()
. Вы закончите, используя fork()
для создания дочернего процесса другой программы Node.js, с которой вы можете коммуницировать по мере ее выполнения. Для иллюстрации этих концепций, вы напишете программу, чтобы перечислить содержание каталога, программу для поиска файлов и веб-сервер с несколькими конечными точками.
У вас должен быть установлен Node.js для запуска этих примеров. В этом обучающем руководстве используется версия 10.22.0. Чтобы установить его в macOS или Ubuntu 18.04, следуйте указаниям руководства Установка Node.js и создание локальной среды разработки в macOS или раздела Установка с помощью PPA руководства Установка Node.js в Ubuntu 18.04.
В этой статье используется пример, который создает веб-сервер для объяснения того, как работает функция fork()
. Для ознакомления с процедурой создания веб-серверов можно прочитать наше руководство Создание веб-сервера в Node.js с помощью модуля HTTP.
exec()
Разработчики обычно создают дочерние процессы для выполнения команд в операционной системе, когда необходимо проводить манипуляции с выводом их программ Node.js с помощью оболочки, например при использовании передачи или перенаправления оболочки. Функция exec()
в Node.js создает новый процесс оболочки и выполняет команду в этой оболочке. Вывод команды хранится в буфере в памяти, который вы можете принимать с помощью функции обратного вызова, передаваемой в exec()
.
Давайте начнем создание первых дочерних процессов в Node.js. Для начала нам нужно настроить среду кодирования для хранения скриптов, которые будут создаваться в данном обучающем руководстве. Создайте в терминале папку с именем child-processes
:
- mkdir child-processes
Войдите в эту папку в терминале с помощью команды cd
:
- cd child-processes
Создайте новый файл с именем listFiles.js
и откройте файл в текстовом редакторе. В этом обучающем руководстве мы будем использовать nano, текстовый редактор терминала:
- nano listFiles.js
Мы будем писать модуль Node.js, который использует функцию exec()
для запуска команды ls
. Команда ls
перечисляет файлы и папки в каталоге. Эта программа принимает вывод из команды ls
и отображает его пользователю.
В текстовом редакторе добавьте следующий код:
const { exec } = require('child_process');
exec('ls -lh', (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
console.log(`stdout:\n${stdout}`);
});
Сначала мы импортируем функцию exec()
из модуля child_process
с помощью деструктурирования JavaScript. После импорта мы используем функцию exec()
. Первым аргументом является команда, которую мы хотим запустить. В этом случае это ls -lh
, которая перечисляет все файлы и папки в текущем каталоге в длинном формате с общим размером файла в удобочитаемых единицах в верхней части вывода.
Второй аргумент — это функция обратного вызова с тремя параметрами: error
, stdout
и stderr
. Если команда не запустилась, error
выявит причину сбоя. Это может произойти, если оболочка не может найти команду, которую вы пытаетесь выполнить. Если команда выполняется успешно, все данные, которые она записывает в стандартный поток вывода, записываются в stdout
, а все данные, которые она записывает в стандартный поток ошибок, записываются в stderr
.
Примечание. Важно запомнить разницу между error
и stderr
. Если сама команда не запустилась, error
запишет ошибку. Если команда выполняется, но возвращает вывод в поток ошибок, stderr
запишет ее. Самые устойчивые программы Node.js будут обрабатывать все возможные выводы для дочернего процесса.
В функции обратного вызова мы сначала проверим, получена ли ошибка. Если получена, мы отобразим message
ошибки (свойство объекта Error
) с console.error()
и завершим функцию с помощью return
. Затем мы проверим, напечатала ли команда сообщение об ошибке и return
, если это так. Если команда успешно выполняется, мы запишем ее вывод в консоль с помощью console.log()
.
Давайте запустим этот файл, чтобы увидеть его в действии. Сначала сохраните и закройте nano
, нажав CTRL+X
.
Вернувшись в терминал, запустите свое приложение с помощью команды node
:
- node listFiles.js
Ваш терминал отобразит следующий вывод:
Outputstdout:
total 4.0K
-rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js
В нем перечисляется содержимое каталога child-processes
в длинном формате вместе с размером содержимого в верхней части. Результаты будут содержать собственного пользователя и группу вместо sammy
. Это показывает, что программа listFiles.js
успешно запустила команду оболочки ls -lh
.
Теперь давайте рассмотрим другой способ выполнения параллельных процессов. Модуль child_process
в Node.js также может запускать исполняемые файлы с функцией execFile()
. Ключевая разница между функциями execFile()
и exec()
заключается в том, что первый аргумент execFile()
теперь является путем к исполняемому файлу вместо команды. Вывод исполняемого файла хранится в буфере, например exec()
, доступ к которому мы получаем с помощью функции обратного вызова с параметрами error
, stdout
и stderr
.
Примечание. Скрипты в Windows, например файлы .bat
и .cmd
, нельзя запустить с помощью execFile()
, поскольку функция не создает оболочку при запуске файла. В Unix, Linux и macOS исполняемым скриптам не всегда требуется оболочка для запуска. Однако на компьютерах с Windows требуется оболочка для выполнения скриптов. Для исполнения файлов скрипта в Windows используйте функцию exec()
, поскольку она создает новую оболочку. Также вы можете использовать команду spawn()
, которую вы будете использовать далее в этом шаге.
Тем не менее обратите внимание, что вы можете успешно выполнять файлы .exe
в Windows с помощью execFile()
. Это ограничение распространяется только на файлы скрипта, для выполнения которых требуется оболочка.
Начнем с добавления исполняемого скрипта для запуска execFile()
. Мы напишем скрипт bash, который загрузит логотип Node.js с сайта Node.js, а Base64 зашифрует его для преобразования данных в строку символов ASCII.
Создайте новый файл скрипта с оболочкой с именем processNodejsImage.sh
:
- nano processNodejsImage.sh
Теперь напишите скрипт для загрузки образа и его конвертации base64:
#!/bin/bash
curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg
base64 nodejs-logo.svg
Первым выражением является выражение shebang. Оно используется в Unix, Linux и macOS, когда необходимо указать оболочку для выполнения нашего скрипта. Второе выражение — это команда curl
. Утилита cURL, чьей командой является curl
, — это инструмент командной строки, который может передавать данные на сервер и с него. Мы используем cURL для загрузки логотипа Node.js с сайта, а затем используем перенаправление для сохранения загруженных данных в новый файл nodejs-logo.svg
. В последнем выражении используется утилита base64
для кодирования файла nodejs-logo.svg
, который мы загрузили с помощью cURL. Затем скрипт выводит закодированную строку в консоль.
Сохраните и закройте перед продолжением.
Чтобы наша программа Node запустила скрипт bash, нам нужно сделать его исполняемым. Для этого запустите следующую команду:
- chmod u+x processNodejsImage.sh
Это даст вашему текущему пользователю разрешение выполнять файл.
После подготовки скрипта мы можем написать новый модуль Node.js для его выполнения. Этот скрипт будет использовать execFile()
для запуска скрипта в дочернем процессе, фиксируя все ошибки и отображая все выводы в консоли.
В своем терминале создайте новый файл JavaScript с именем getNodejsImage.js
:
- nano getNodejsImage.js
Введите в текстовом редакторе следующий код:
const { execFile } = require('child_process');
execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
console.log(`stdout:\n${stdout}`);
});
Мы используем деструктурирование JavaScript для импорта функции execFile()
из модуля child_process
. Затем мы используем эту функцию, передав путь к файлу в качестве первого имени. __dirname
содержит путь к каталогу модуля, в котором он написан. Node.js предоставляет переменную __dirname
для модуля, когда модуль запускается. С помощью __dirname
наш скрипт всегда будет находить файл processNodejsImage.sh
в различных операционных системах, независимо от того, где мы запускаем getNodejsImage.js
. Обратите внимание, что для настройки нашего текущего проекта getNodejsImage.js
и processNodejsImage.sh
должны находиться в одной папке.
Второй аргумент — это обратный вызов с параметрами error
, stdout
и stderr
. Как и в предыдущем примере, в котором использовалась функция exec()
, мы проверяем все возможные выводы файла скрипта и записываем их в консоль.
В текстовом редакторе сохраните этот файл и закройте редактор.
В своем терминале используйте node
для выполнения модуля:
- node getNodejsImage.js
При запуске этого скрипта будет сгенерирован следующий вывод:
Outputstdout:
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge
...
Обратите внимание, что в этой статье мы обрезали вывод из-за большого размера.
Перед тем как base64 закодирует изображение, processNodejsImage.sh
сначала загрузит его. Также вы можете убедиться, что изображение загружено, проверив текущий каталог.
Выполните listFiles.js
, чтобы найти обновленный список файлов в нашем каталоге:
- node listFiles.js
Скрипт отобразит содержимое, аналогичное следующему, в терминале:
Outputstdout:
total 20K
-rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js
-rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js
-rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg
-rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh
Мы успешно выполнили processNodejsImage.sh
в качестве дочернего процесса в Node.js с помощью функции execFile()
.
Функции exec()
и execFile()
могут запускать команды в оболочке операционной системы в дочернем процессе Node.js. Node.js также предоставляет другой метод с аналогичными функциями, spawn()
. Разница в том, что вместо получения сразу всего вывода команд оболочки мы получим его фрагментами с помощью потока. В следующем разделе мы будем использовать команду spawn()
для создания дочернего процесса.
spawn()
Функция spawn()
запускает команду в процессе. Эта функция возвращает данные через поток API. Поэтому для получения вывода дочернего процесса нам потребуется прослушать события потока.
Потоки в Node.js — это экземпляры отправителей событий. Для получения дополнительной информации о прослушивании событий и основах взаимодействия с потоками, ознакомьтесь с нашим руководством Использование отправителей событий в Node.js.
Зачастую лучше выбрать spawn()
вместо exec()
или execFile()
, когда команда, которую вы хотите запустить, может вывести большое количество данных. С помощью буфера, как и в случае использования exec()
и execFile()
, все обрабатываемые данные хранятся в памяти компьютера. Для больших объемов данных это может привести к снижению производительности системы. С помощью потока данные обрабатываются и передаются небольшими фрагментами. Поэтому вы можете обрабатывать большое количество данных, не используя слишком большой объем памяти одновременно.
Посмотрим, как можно использовать spawn()
для создания дочернего процесса. Мы напишем новый модуль Node.js, который создает дочерний процесс для запуска команды find
. Мы будем использовать команду find
для перечисления всех файлов в текущем каталоге.
Создайте новый файл с именем findFiles.js
:
- nano findFiles.js
В текстовом редакторе сначала вызовем команду spawn()
:
const { spawn } = require('child_process');
const child = spawn('find', ['.']);
Сначала мы импортировали функцию spawn()
из модуля child_process
. Затем мы вызвали функцию spawn()
для создания дочернего процесса, который выполняет команду find
. Мы удерживаем ссылку на процесс в переменной child
, которую мы будем использовать для прослушивания потоковых событий.
Первым аргументом в spawn()
является команда для запуска, в данном случае find
. Второй аргумент — это массив, который содержит аргументы для исполняемой команды. В этом случае мы говорим Node.js выполнить команду find
с помощью аргумента .
, тем самым заставляя команду находить все файлы в текущем каталоге. Аналогичной командой в терминале является find .
.
С помощью функций exec()
и execFile()
мы написали аргументы вместе с командой в одной строке. Однако с помощью spawn()
все аргументы для команд должны быть введены в массив. Это потому, что spawn()
, в отличие от exec()
и execFile()
, не создает новую оболочку перед запуском процесса. Чтобы команды находились в одной строке со своими аргументами, необходимо, чтобы Node.js также создал новую оболочку.
Продолжим работу над модулем, добавив слушателей для вывода команды. Добавьте следующие выделенные строки:
const { spawn } = require('child_process');
const child = spawn('find', ['.']);
child.stdout.on('data', data => {
console.log(`stdout:\n${data}`);
});
child.stderr.on('data', data => {
console.error(`stderr: ${data}`);
});
Команды могут возвращать данные в потоке stdout
или потоке stderr
, поэтому вы добавили слушателей к ним обоим. Вы можете добавить слушателей, вызвав метод on()
объектов всех потоков. Событие data
из потоков дает нам вывод команды к этому потоку. Каждый раз, когда мы получаем данные от того или иного потока, мы записываем их в консоль.
Затем мы будем слушать два других события: событие error
, если команда не выполняется или прерывается, и событие close
, если команда завершила выполнение, таким образом закрывая поток.
В текстовом редакторе завершите модуль Node.js, написав следующие выделенные строки:
const { spawn } = require('child_process');
const child = spawn('find', ['.']);
child.stdout.on('data', (data) => {
console.log(`stdout:\n${data}`);
});
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child.on('error', (error) => {
console.error(`error: ${error.message}`);
});
child.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
Для событий error
и close
вы назначаете слушателя непосредственно в переменной child
. При прослушивании событий error
, если одно из них происходит, Node.js предоставляет объект Error
. В этом случае вы регистрируете свойство message
ошибки.
При прослушивании события close
Node.js предоставляет код выхода команды. Код выхода указывает на то, успешно ли была запущена команда. Когда команда запускается без ошибок, она возвращает наименьшее возможное значение для кода выхода: 0
. При выполнении с ошибкой она возвращает ненулевой код.
Модуль завершен. Сохраните и закройте nano
с помощью CTRL+X
.
Теперь запустите код с помощью команды node
:
- node findFiles.js
После выполнения вы получите следующий вывод:
Outputstdout:
.
./findFiles.js
./listFiles.js
./nodejs-logo.svg
./processNodejsImage.sh
./getNodejsImage.js
child process exited with code 0
Мы находим список всех файлов в текущем каталоге и код выхода команды, то есть 0
, так как она была запущена успешно. Хотя в нашем текущем каталоге находится небольшое количество файлов, если мы запустим этот код в домашнем каталоге, наша программа укажет каждый файл в каждой доступной пользователю папке. Поскольку потенциально вывод может оказаться большим, наиболее оптимальным будет использование функции spawn()
, так как для ее потоков не требуется столько памяти, сколько может обеспечить буфер.
Пока мы использовали функции для создания дочерних процессов для выполнения внешних команд в нашей операционной системе. Node.js также обеспечивает способ создать дочерний процесс, который выполняет другие программы Node.js. Давайте используем функцию fork()
для создания дочернего процесса для модуля Node.js в следующем разделе.
fork()
Node.js предоставляет функцию fork()
, вариант spawn()
, для создания дочернего процесса, который также является процессом Node.js. Главным преимуществом использования fork()
для создания процесса Node.js над spawn()
или exec()
является то, что fork()
обеспечивает коммуникацию между родительским и дочерним процессом.
С помощью fork()
, помимо извлечения данных из дочернего процесса, родительский процесс может отправлять сообщения в выполняемый дочерний процесс. Аналогичным образом дочерний процесс может отправлять сообщения в родительский процесс.
Давайте рассмотрим пример, где использование fork()
для создания нового дочернего процесса Node.js может повысить производительность нашего приложения. Программы Node.js запускаются в рамках одного процесса. Поэтому ресурсоемкие задачи процессора, такие как итерация по большим циклам или синтаксический анализ крупных файлов JSON, останавливают выполнение другого кода JavaScript. Для определенных приложений этот вариант нецелесообразен. Если веб-сервер заблокирован, он не сможет обрабатывать новые входящие запросы до тех пор, пока блокирующий код не завершит выполнение.
Давайте рассмотрим это на практике, создав веб-сервер с двумя конечными точками. Одна конечная точка будет выполнять медленные вычисления, которые блокируют процесс Node.js. Другая конечная точка вернет объект JSON hello
.
Сначала создайте новый файл с именем httpServer.js
, который будет содержать код для нашего сервера HTTP:
- nano httpServer.js
Для начала мы настроим сервер HTTP. Это предполагает импорт модуля http
, создание функции прослушивания запроса, создание объекта сервера и прослушивание запросов на объекте сервера. Если вы хотите подробнее узнать о создании серверов HTTP в Node.js или освежить свои знания, ознакомьтесь с нашим руководством Создание веб-сервера в Node.js с помощью модуля HTTP.
Введите в текстовом редакторе следующий код для настройки сервера HTTP:
const http = require('http');
const host = 'localhost';
const port = 8000;
const requestListener = function (req, res) {};
const server = http.createServer(requestListener);
server.listen(port, host, () => {
console.log(`Server is running on http://${host}:${port}`);
});
Этот код настраивает сервер HTTP, который будет запускаться на http://localhost:8000
. Он использует литералы шаблонов для динамического генерирования этого URL.
Далее мы напишем намеренно медленную функцию, которая выполняет вычисления циклами 5 миллиардов раз. Перед функцией requestListener()
добавьте следующий код:
...
const port = 8000;
const slowFunction = () => {
let counter = 0;
while (counter < 5000000000) {
counter++;
}
return counter;
}
const requestListener = function (req, res) {};
...
В нем используется синтаксис функции arrow для создания цикла while
, который считает до 5000000000
.
Чтобы завершить этот модуль, нам нужно добавить код в функцию requestListener()
. Наша функция вызовет slowFunction()
на подветви и вернет небольшое сообщение JSON для другой. Добавьте в модуль следующий код:
...
const requestListener = function (req, res) {
if (req.url === '/total') {
let slowResult = slowFunction();
let message = `{"totalCount":${slowResult}}`;
console.log('Returning /total results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(message);
} else if (req.url === '/hello') {
console.log('Returning /hello results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(`{"message":"hello"}`);
}
};
...
Если пользователь достигает сервер на подветви /total
, мы запустим slowFunction()
. Если мы попали в подветвь /hello
, мы вернем это сообщение JSON {"message":"hello"}
.
Сохраните и закройте файл, нажав CTRL+X
.
Чтобы протестировать, запустите этот модуль сервера с помощью node
:
- node httpServer.js
Когда наш сервер запускается, консоль отобразит следующее:
OutputServer is running on http://localhost:8000
Теперь, чтобы протестировать производительность нашего модуля, откройте два дополнительных терминала. В первом терминале используйте команду curl
, чтобы сделать запрос в конечную точку /total
, который предположительно будет медленным:
- curl http://localhost:8000/total
В другом терминале используйте curl
, чтобы сделать запрос в конечную точку /hello
следующим образом:
- curl http://localhost:8000/hello
Первый запрос вернет следующий JSON:
Output{"totalCount":5000000000}
А второй запрос вернет следующий JSON:
Output{"message":"hello"}
Запрос в /hello
будет выполнен только после запроса в /total
. slowFunction()
заблокировала выполнение всех других кодов, так как все еще выполняется ее цикл. Проверить это можно, посмотрев на вывод сервера Node.js, который был записан в оригинальном терминале:
OutputReturning /total results
Returning /hello results
Для обработки блокирующего кода, пока он все еще принимает входящие запросы, мы можем переместить блокирующий код в дочерний процесс с помощью fork()
. Мы переместим блокирующий код в его собственный модуль. Сервер Node.js затем создаст дочерний процесс, когда кто-то будет получать доступ к конечной точке /total
, и будет слушать результаты этого дочернего процесса.
Перепроектируйте сервер, сначала создав новый модуль с именем getCount.js
, который будет содержать slowFunction()
:
- nano getCount.js
Теперь снова введите код slowFunction()
:
const slowFunction = () => {
let counter = 0;
while (counter < 5000000000) {
counter++;
}
return counter;
}
Поскольку этот модуль будет дочерним процессом, созданным с помощью fork()
, мы также можем добавить код для коммуникации с родительским процессом, когда slowFunction()
завершит обработку. Добавьте следующий блок кода, который отправляет сообщение в родительский процесс с помощью JSON для возврата к пользователю:
const slowFunction = () => {
let counter = 0;
while (counter < 5000000000) {
counter++;
}
return counter;
}
process.on('message', (message) => {
if (message == 'START') {
console.log('Child process received START message');
let slowResult = slowFunction();
let message = `{"totalCount":${slowResult}}`;
process.send(message);
}
});
Давайте разберем этот блок кода. Сообщения между родительским и дочерним процессами
, созданные fork()
, доступны через глобальный объект process Node.js. Мы добавим слушателя в переменную process
для поиска событий message
. Получив событие message
, мы проверим, является ли оно событием START
. Наш код сервера будет отправлять событие START
, когда кто-то будет получать доступ к конечной точке /total
. После получения этого события мы запускаем slowFunction()
и создаем строку JSON с результатом функции. Мы используем process.send()
для отправки сообщения в родительский процесс.
Сохраните и закройте getCount.js
, введя CTRL+X
в nano.
Теперь давайте изменим файл httpServer.js
, чтобы вместо вызова slowFunction()
он создавал дочерний процесс, который выполняет getCount.js
.
Откройте повторно httpServer.js
с помощью nano
:
- nano httpServer.js
Сначала импортируйте функцию fork()
из модуля child_process
:
const http = require('http');
const { fork } = require('child_process');
...
Далее мы удалим slowFunction()
из этого модуля и изменим функцию requestListener()
для создания дочернего процесса. Измените код в вашем файле, чтобы он выглядел следующим образом:
...
const port = 8000;
const requestListener = function (req, res) {
if (req.url === '/total') {
const child = fork(__dirname + '/getCount');
child.on('message', (message) => {
console.log('Returning /total results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(message);
});
child.send('START');
} else if (req.url === '/hello') {
console.log('Returning /hello results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(`{"message":"hello"}`);
}
};
...
Теперь, когда кто-то попадет в конечную точку /total
, мы создаем новый дочерний процесс с помощью fork()
. Аргумент fork()
— это путь к модулю Node.js. В данном случае это файл getCount.js
в нашем текущем каталоге, который мы получаем от __dirname
. Ссылка на этот дочерний процесс хранится в переменной child
.
Затем мы добавим слушателя в объект child
. Этот слушатель фиксирует все сообщения, которые нам дает дочерний процесс. В данном случае getCount.js
вернет строку JSON с общим числом, которое подсчитал цикл while
. Когда мы получим это сообщение, мы отправим JSON пользователю.
Мы используем функцию send()
переменной child
для передачи ей сообщения. Эта программа отправляет сообщение START
, которое начинает выполнение slowFunction()
в дочернем процессе.
Сохраните и закройте nano
, нажав CTRL+X
.
Чтобы протестировать усовершенствование с помощью fork()
, выполненной на сервере HTTP, начните с выполнения файла httpServer.js
с помощью node
:
- node httpServer.js
Как и ранее, при запуске будет выведено следующее сообщение:
OutputServer is running on http://localhost:8000
Чтобы протестировать сервер, нам понадобится два дополнительных терминала, как и в первый раз. Вы можете повторно их использовать, если они все еще открыты.
В первом терминале используйте команду curl
, чтобы сделать запрос в конечную точку /total
, которой потребуется какое-то время для вычислений:
- curl http://localhost:8000/total
В другом терминале используйте curl
, чтобы сделать запрос в конечную точку /hello
, которая отвечает быстро:
- curl http://localhost:8000/hello
Первый запрос вернет следующий JSON:
Output{"totalCount":5000000000}
Тогда как второй запрос вернет следующий JSON:
Output{"message":"hello"}
В отличие от того, как мы делали это в первый раз, второй запрос в /hello
запускается немедленно. Вы можете убедиться, проверив журналы, которые выглядят следующим образом:
OutputChild process received START message
Returning /hello results
Returning /total results
Эти журналы показывают, что запрос в конечную точку /hello
был запущен после создания дочернего процесса, но до того как дочерний процесс завершил свою задачу.
Поскольку мы переместили блокирующий код в дочерний процесс с помощью fork()
, сервер все еще мог реагировать на другие запросы и выполнять другой код JavaScript. Из-за передающей способности сообщения функции fork()
мы можем контролировать, когда начинает активность дочерний процесс, и мы можем вернуть данные из дочернего процесса в родительский процесс.
В этой статье вы использовали различные функции для создания дочернего процесса в Node.js. Сначала вы создали дочерние процессы с помощью exec()
для запуска команд оболочки из кода Node.js. Затем вы запустили исполняемый файл с помощью функции execFile()
. Вы рассмотрели функцию spawn()
, которая также может запускать команды, но возвращает данные через поток и не запускает оболочку, как exec()
и execFile()
. Наконец, вы использовали функцию fork()
для обеспечения двусторонней связи между родительским и дочерним процессом.
Дополнительную информацию о модуле child_process
можно найти в документации Node.js. Если хотите продолжить изучение Node.js, то можете вернуться к серии Программирование на Node.js или ознакомиться с проектами и конфигурациями на нашей странице разделов Node.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!