Мы все знаем, что Node.js использует однопоточную модель асинхронного ввода-вывода, управляемую событиями. Ее характеристики определяют, что он не может использовать преимущества многоядерного процессора и плохо справляется с выполнением некоторых операций, не связанных с вводом-выводом ( такие как выполнение сценариев), вычисления с использованием искусственного интеллекта, обработка изображений и т. д.), для решения таких проблем Node.js предоставляет обычное многопроцессное (потоковое) решение (обсуждение процессов и потоков см. в книге автора). другая статья Node.js и модель параллелизма), эта статья познакомит вас с многопоточным механизмом Node.js.
Мы можем использовать модуль child_process
для создания дочернего процесса Node.js для выполнения некоторых специальных задач (например, выполнения сценариев). Этот модуль в основном предоставляет методы exec
, execFile
, fork
, spwan
и другие. Ниже мы кратко представим эти методы. . использовать.
const { exec } = require('child_process'); exec('ls -al', (ошибка, стандартный вывод, стандартный вывод) => { console.log(stdout); });
Этот метод обрабатывает командную строку в соответствии с исполняемым файлом, указанным в options.shell
, кэширует ее вывод во время выполнения команды, а затем возвращает результат выполнения в виде параметров функции обратного вызова до завершения выполнения команды.
Параметры этого метода описаны следующим образом:
command
: команда, которую необходимо выполнить (например, ls -al
)
options
настройки параметров (необязательно), соответствующие свойства следующие:
cwd
: текущий рабочий каталог дочернего процесса; значением по умолчанию является process.cwd()
;
env
: настройка переменной среды (объект пары ключ-значение), значением по умолчанию является значение process.env
;
encoding
: кодировка символов, значение по умолчанию: utf8
;
shell
: исполняемый файл
файл cmd.exe
обрабатывающий командные строки, значение по умолчанию в Unix
— /bin/sh
, значение по умолчанию в Windows
— значениеprocess.env.ComSpec (если оно пусто, это process.env.ComSpec
, например:
const { exec);
} = require('child_process'); exec("print('Hello World!')", {shell: 'python' }, (ошибка, stdout, stderr) => { console.log(stdout); });
Выполнение приведенного выше примера приведет к выводу Hello World!
что эквивалентно подпроцессу, выполняющему команду python -c "print('Hello World!')"
. Поэтому при использовании этого атрибута вам необходимо обратить внимание на. указанный исполняемый файл. Должно поддерживаться выполнение связанных операторов с помощью опции -c
.
Примечание. Бывает, что Node.js
также поддерживает параметр -c
, но он эквивалентен параметру --check
. Он используется только для определения наличия синтаксических ошибок в указанном сценарии и не будет выполнять соответствующий сценарий.
signal
: используйте указанный AbortSignal для завершения дочернего процесса. Этот атрибут доступен выше версии 14.17.0, например:
const { exec } = require('child_process'); const ac = новый AbortController(); exec('ls -al', { signal: ac.signal }, (error, stdout, stderr) => {});
В приведенном выше примере мы можем досрочно завершить дочерний процесс, вызвав ac.abort()
.
timeout
: время ожидания дочернего процесса (если значение этого атрибута больше 0
, то когда время работы дочернего процесса превысит указанное значение, дочернему процессу будет отправлен сигнал завершения, указанный атрибутом killSignal
), в миллиметрах, значение по умолчанию — 0
;
maxBuffer
: Максимальный размер кэша (двоичный), разрешенный стандартным выводом или stderr. При превышении дочерний процесс будет уничтожен, а любой вывод будет усечен. Значение по умолчанию — 1024 * 1024
killSignal
: сигнал завершения дочернего процесса, значение по умолчанию: SIGTERM
;
uid
: uid
для выполнения дочернего процесса;
gid
: gid
для выполнения дочернего процесса;
windowsHide
: скрывать ли окно консоли дочернего процесса, обычно используемое в системах Windows
, значение по умолчанию — false
;
callback
: функция обратного вызова, включая error
, stdout
, stderr
Параметры:
error
: если командная строка выполнена успешно, значение равно null
, в противном случае значением является экземпляр Error, где error.code
— выход. код ошибки дочернего процесса, error.signal
— это сигнал завершения дочернего процесса;stdout
и stderr
: child. stdout
и stderr
процесса кодируются в соответствии со значением атрибута encoding
, если значением encoding
является buffer
. , или значение stdout
или stderr
представляет собой нераспознаваемую строку, оно будет закодировано в соответствии с buffer
.const { execFile } = require('child_process'); execFile('ls', ['-al'], (ошибка, stdout, stderr) => { console.log(stdout); });
Функция этого метода аналогична exec
, с той лишь разницей, что execFile
по умолчанию обрабатывает команду напрямую с указанным исполняемым файлом (то есть значением параметра file
), что делает его эффективность несколько выше, чем exec
(если вы посмотрите на логику обработки оболочки, я чувствую, что эффективность незначительна).
Параметры этого метода описаны следующим образом:
file
: имя или путь к исполняемому файлу;
args
: список параметров исполняемого файла;
options
: настройки параметров (нельзя указать), соответствующие свойства следующие:
shell
: когда значение false
это означает прямое использование указанного исполняемого файла (то есть значения file
параметра) обрабатывает команду. Если значение true
или другие строки, функция эквивалентна shell
в exec
. Значение по умолчанию — false
;windowsVerbatimArguments
: заключать или экранировать параметры в Windows
. Этот атрибут будет игнорироваться в Unix
, а значение по умолчанию — false
;cwd
, env
, encoding
, timeout
, maxBuffer
, killSignal
, uid
, gid
, windowsHide
и signal
были представлены выше и здесь не будут повторяться.callback
: функция обратного вызова, которая эквивалентна callback
в exec
и не будет здесь объясняться.
const { fork } = require('child_process'); const echo = fork('./echo.js', { молчание: правда }); echo.stdout.on('данные', (данные) => { console.log(`stdout: ${data}`); }); echo.stderr.on('данные', (данные) => { console.error(`stderr: ${data}`); }); echo.on('закрыть', (код) => { console.log(`дочерний процесс завершился с кодом ${code}`); });
Этот метод используется для создания нового экземпляра Node.js для выполнения указанного сценария Node.js и взаимодействия с родительским процессом через IPC.
Параметры этого метода описаны следующим образом:
modulePath
: путь к скрипту Node.js, который нужно запустить;
args
: список параметров, передаваемых в скрипт Node.js;
options
: настройки параметров (нельзя указать), связанные атрибуты. например:
detached
: см. spwan
описание options.detached
;
execPath
: создать исполняемый файл дочернего процесса;
execArgv
: список строковых параметров, передаваемых в исполняемый файл, значением по умолчанию является process.execArgv
serialization
: Тип серийного номера межпроцессного сообщения, доступные значения — json
и advanced
, значение по умолчанию — json
;
slient
: Если true
, stdin
, stdout
и stderr
дочернего процесса будут переданы родительскому процессу. через каналы, иначе будут унаследованы stdin
, stdout
и stderr
родительского процесса; значение по умолчанию — false
;
stdio
: см. описание options.stdio
в spwan
ниже; Здесь необходимо отметить, что
slient
будет игнорироваться;ipc
(например, [0, 1, 2, 'ipc']
), в противном случае будет выброшено исключение.Свойства cwd
, env
, uid
, gid
, windowsVerbatimArguments
, signal
, timeout
и killSignal
были представлены выше и не будут здесь повторяться.
const {spawn} = require('child_process'); const ls = spawn('ls', ['-al']); ls.stdout.on('данные', (данные) => { console.log(`stdout: ${data}`); }); ls.stderr.on('данные', (данные) => { console.error(`stderr: ${data}`); }); ls.on('закрыть', (код) => { console.log(`дочерний процесс завершился с кодом ${code}`); });
Этот метод является базовым методом модуля child_process
. exec
, execFile
и fork
в конечном итоге вызовут spawn
для создания дочернего процесса.
Параметры этого метода объясняются следующим образом:
command
: имя или путь к исполняемому файлу;
args
: список параметров, передаваемых в исполняемый файл;
options
: настройки параметров (нельзя указать), соответствующие атрибуты следующие:
argv0
: отправляется дочернему процессу значение argv[0 ], значением по умолчанию является значение параметра command
;
detached
: разрешить ли дочернему процессу работать независимо от родительского процесса (т. процесс может продолжать выполняться), значение по умолчанию — false
, и когда его значение равно true
, каждая платформа. Эффект следующий:
Windows
после завершения родительского процесса дочерний процесс может продолжать работать, а дочерний процесс может продолжать выполняться. имеет собственное окно консоли (после запуска этой функции ее нельзя изменить во время запущенного процесса).Windows
, в это время дочерний процесс будет служить лидером новой группы сеансов процесса. Независимо от того, отделен ли дочерний процесс от родительского процесса, дочерний процесс может продолжать работать после завершения родительского процесса.Следует отметить, что если дочернему процессу необходимо выполнить долгосрочную задачу и он хочет, чтобы родительский процесс завершился раньше, необходимо одновременно выполнить следующие условия:
unref
дочернего процесса для удаления дочернего процесса. процесс из цикла событий родительского процесса отключен.detached
значение true
;stdio
ignore
.Например, следующий пример:
// hello.js const fs = require('fs'); пусть индекс = 0; функция запуска() { setTimeout(() => { fs.writeFileSync('./hello', `index: ${index}`); если (индекс < 10) { индекс += 1; бегать(); } }, 1000); } бегать(); // main.js const {spawn} = require('child_process'); const child = spawn('node', ['./hello.js'], { отстраненный: правда, stdio: 'игнорировать' }); child.unref();
stdio
: стандартная конфигурация ввода и вывода дочернего процесса, значением по умолчанию является pipe
, значением является строка или массив:
pipe
преобразуется в ['pipe', 'pipe', 'pipe']
), доступные значения: pipe
, overlapped
, ignore
, inherit
,stdin
, stdout
и stderr
соответственно, каждый. Доступные значения элемента: pipe
, overlapped
, ignore
, inherit
, ipc
, Stream object, положительное целое число (дескриптор файла, открытый в родительском процессе), null
(если это так). расположен в первых трёх элементах массива, эквивалентен pipe
, в противном случае эквивалентен ignore
), undefined
(если он находится в первых трёх элементах массива, он эквивалентен pipe
, в противном случае эквивалентен ignore
).Атрибуты cwd
, env
, uid
, gid
, serialization
, shell
(значение boolean
или string
), windowsVerbatimArguments
, windowsHide
, signal
, timeout
, killSignal
были представлены выше и не будут здесь повторяться.
Выше приведено краткое введение в использование основных методов в модуле child_process
. Поскольку методы execSync
, execFileSync
, forkSync
и spwanSync
являются синхронными версиями exec
, execFile
и spwan
, в их параметрах нет никакой разницы, поэтому они не будут повторяться.
С помощью модуля cluster
мы можем создать кластер процессов Node.js. Добавив процесс Node.js в кластер, мы можем полнее использовать преимущества многоядерности и распределять программные задачи по разным процессам для улучшения выполнения. эффективность программы, которую мы будем использовать ниже. В этом примере показано использование модуля cluster
:
const http = require('http'); const кластер = требуется ('кластер'); const numCPUs = require('os').cpus().length; если (cluster.isPrimary) { for (let i = 0; i <numCPUs; i++) { кластер.вилка(); } } еще { http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); }).слушать(8000); }
Приведенный выше пример разделен на две части на основе оценки атрибута cluster.isPrimary
(то есть определения того, является ли текущий процесс основным):
cluster.fork
.8000
).Запустите приведенный выше пример и получите доступ к http://localhost:8000/
в браузере. Мы обнаружим, что возвращаемый pid
различен для каждого доступа, что показывает, что запрос действительно распространяется на каждый дочерний процесс. Стратегией балансировки нагрузки по умолчанию, принятой в Node.js, является циклическое планирование, которое можно изменить с помощью переменной среды NODE_CLUSTER_SCHED_POLICY
или свойства cluster.schedulingPolicy
:
NODE_CLUSTER_SCHED_POLICY = rr // или none кластер.schedulingPolicy = кластер.SCHED_RR; // или кластер.SCHED_NONE
Следует также отметить, что хотя каждый дочерний процесс создал HTTP-сервер и прослушивает один и тот же порт, это не означает, что эти дочерние процессы могут конкурировать за него. запросы пользователей, поскольку это не может гарантировать, что нагрузка всех дочерних процессов будет сбалансирована. Следовательно, правильный процесс должен заключаться в том, чтобы основной процесс прослушивал порт, а затем пересылал запрос пользователя в определенный подпроцесс для обработки в соответствии с политикой распределения.
Поскольку процессы изолированы друг от друга, процессы обычно взаимодействуют через такие механизмы, как общая память, передача сообщений и каналы. Node.js завершает связь между родительским и дочерним процессами посредством消息传递
, как показано в следующем примере:
const http = require('http'); const кластер = требуется ('кластер'); const numCPUs = require('os').cpus().length; если (cluster.isPrimary) { for (let i = 0; i <numCPUs; i++) { константный рабочий = кластер.форк(); worker.on('сообщение', (сообщение) => { console.log(`Я основной(${process.pid}), я получил сообщение от работника: "${message}"`); worker.send(`Отправить сообщение работнику`) }); } } еще { process.on('сообщение', (сообщение) => { console.log(`Я рабочий(${process.pid}), я получил сообщение от основного: "${message}"`) }); http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); process.send('Отправить сообщение основному пользователю'); }).слушать(8000); }
Запустите приведенный выше пример и посетите http://localhost:8000/
, затем проверьте терминал, мы увидим вывод, аналогичный следующему:
Я основной (44460), я получил сообщение от работника: «Отправить сообщение основному» Я работник (44461), я получил сообщение от основного: «Отправить сообщение работнику». Я основной (44460), я получил сообщение от работника: «Отправить сообщение основному» Я рабочий (44462), я получил сообщение от основного: «Отправить сообщение рабочему».
Используя этот механизм, мы можем отслеживать состояние каждого дочернего процесса, чтобы при возникновении аварии в дочернем процессе мы могли вовремя вмешаться в него. для обеспечения доступности Услуг.
Интерфейс модуля cluster
очень прост. В целях экономии места здесь мы делаем только некоторые специальные утверждения о методе cluster.setupPrimary
. Для других методов, пожалуйста, проверьте официальную документацию:
cluster.setupPrimary
соответствующие настройки. будет синхронизирован с атрибутом cluster.settings
, и каждый вызов основан на значении текущего атрибута cluster.settings
cluster.setupPrimary
он не влияет на запущенный дочерний процесс, а только последующие вызовы cluster.fork
затрагиваются;cluster.setupPrimary
это не влияет на последующие переходы к cluster.fork
Параметр env
вызоваcluster.setupPrimary
можно использовать только в основном процессе.Ранее мы представили модуль cluster
, с помощью которого мы можем создать кластер процессов Node.js для повышения эффективности работы программы. Однако cluster
основан на многопроцессной модели с дорогостоящим переключением между процессами и изоляцией. ресурсов между процессами Увеличение количества дочерних процессов может легко привести к проблеме невозможности ответа из-за ограничений системных ресурсов. Для решения таких проблем Node.js предоставляет worker_threads
. Ниже мы кратко представим использование этого модуля на конкретных примерах:
// server.js. const http = require('http'); const {Worker} = require('worker_threads'); http.createServer((req, res) => { const httpWorker = новый работник('./http_worker.js'); httpWorker.on('сообщение', (результат) => { res.writeHead(200); res.end(`${result}n`); }); httpWorker.postMessage('Том'); }).слушать(8000); // http_worker.js const {parentPort} = require('worker_threads'); родительскийПорт.он('сообщение', (имя) => { parentPort.postMessage(`Добро пожаловать ${name}!`); });
В приведенном выше примере показано простое использование worker_threads
. При использовании worker_threads
необходимо обратить внимание на следующие моменты:
Создайте экземпляр Worker через worker_threads.Worker
, где сценарий Worker может быть либо независимым файлом JavaScript
, либо字符串
, например, приведенный выше пример можно изменить так:
const code = "const { родительскийПорт } = require('worker_threads'); родительскийПорт.on('message', (name) => {parentPort.postMessage(`Welcone ${ name}!` );})"; const httpWorker = new Worker(code, { eval: true });
При создании экземпляра Worker через worker_threads.Worker
вы можете установить исходные метаданные подпотока Worker, указав значение workerData
, например:
// server .js const {Worker} = require('worker_threads'); const httpWorker = новый Worker('./http_worker.js', {workerData: {name: 'Tom'} }); // http_worker.js const {workerData} = require('worker_threads'); console.log(workerData);
При создании экземпляра Worker через worker_threads.Worker
вы можете установить SHARE_ENV
, чтобы реализовать необходимость совместного использования переменных среды между подпотоком Worker и основным потоком, например:
const { Worker, SHARE_ENV } = require('worker_threads'); const worker = new Worker('process.env.SET_IN_WORKER = "foo"', {eval: true, env: SHARE_ENV }); worker.on('exit', () => { console.log(process.env.SET_IN_WORKER); });
В отличие от механизма межпроцессного взаимодействия в cluster
, worker_threads
использует MessageChannel для связи между потоками:
parentPort.postMessage
и обрабатывает сообщения из основного message
, прослушивая Событие message
parentPort
;httpWorker
через метод postMessage
экземпляра подпотока Worker (здесь httpWorker
заменяется этим подпотоком Worker ниже) и обрабатывает сообщения из подпотока Worker. прослушивая событие message
httpWorker
.В Node.js, будь то дочерний процесс, созданный cluster
, или дочерний поток Worker, созданный worker_threads
, все они имеют свой собственный экземпляр V8 и цикл событий. Разница в том, что
Хотя кажется, что подпотоки Worker более эффективны, чем дочерние процессы, подпотоки Worker также имеют недостатки, то есть cluster
обеспечивает балансировку нагрузки, тогда как worker_threads
требует от нас самостоятельно завершить проектирование и реализацию балансировки нагрузки.
В этой статье рассказывается об использовании трех модулей child_process
, cluster
и worker_threads
в Node.js. С помощью этих трех модулей мы можем в полной мере использовать преимущества многоядерных процессоров и эффективно решать некоторые специальные проблемы в многопоточном режиме. поток) Режим эффективности работы задач (таких как AI, обработка изображений и т. д.). Каждый модуль имеет свои применимые сценарии. В этой статье объясняется только его основное использование. Как его эффективно использовать в зависимости от ваших собственных проблем, вам еще предстоит изучить самостоятельно. Наконец, если в этой статье есть какие-либо ошибки, я надеюсь, что вы сможете их исправить. Желаю всем вам счастливого программирования каждый день.