оригинал:nodesource.com/blog/worker…
Понимание основных слоев узла необходимо понимать работников.
Когда приложение Node.js запускается, оно запускает следующий модуль:
- процесс
- Тема
- механизм цикла событий
- Пример JS-движка
- Экземпляр Node.js
Процесс: объект процесса — это глобальная переменная, к которой можно получить доступ в любом месте программы Node.js и которая предоставляет информацию о текущем процессе.
Поток: одиночный поток означает, что в текущем процессе одновременно выполняется только одна инструкция.
Цикл событий: это важная часть Node.js, которую необходимо понимать.Хотя JavaScript является однопоточным, с использованием обратных вызовов, промисов, async/await и других синтаксисов, основанных на цикле событий, операция операционной системы является асинхронным, благодаря чему узел имеет функцию асинхронного неблокирующего ввода-вывода.
Экземпляр движка JS: программа, которая может запускать код JavaScript.
Экземпляр Node.js: программа, которая может запускать среду Node.js.
Другими словами, Node работает в одном потоке, и в цикле событий одновременно выполняется только одна задача процесса, и одновременно одновременно выполняется только один фрагмент кода (несколько фрагментов кода не будут выполняться). выполняется одновременно). Это очень эффективно, потому что механизм достаточно прост, и вам не нужно беспокоиться о параллельном программировании при использовании JavaScript.
Причина этого в том, что JavaScript изначально использовался для взаимодействия на стороне клиента (например, взаимодействия с веб-страницей или проверки формы), и эта логика не требовала для обработки такого механизма, как многопоточность.
Таким образом, это также приносит еще один недостаток: если вам нужно использовать задачи с интенсивным использованием ЦП, такие как выполнение сложных вычислений с большим набором данных в памяти, это будет блокировать задачи других процессов. Аналогичным образом, когда вы отправляете запрос к удаленному интерфейсу с задачей, интенсивно использующей ЦП, вы также блокируете другие запросы, которые необходимо выполнить.
Функция считается блокирующей, если она блокирует механизм цикла событий до тех пор, пока функция не завершит выполнение до того, как может быть выполнена следующая функция. Неблокирующая функция не будет блокировать цикл событий от выполнения следующей функции, она будет использовать обратный вызов, чтобы уведомить функцию цикла событий о том, что задача была выполнена.
Рекомендация: не блокируйте цикл обработки событий, продолжайте работу цикла обработки событий и избегайте использования операций, которые блокируют поток в обратном направлении, таких как вызовы синхронного сетевого интерфейса или бесконечные циклы.
Важно различать операции, связанные с ЦП, и операции, связанные с вводом-выводом (вводом-выводом). Как упоминалось ранее, Node.js не выполняет несколько фрагментов кода одновременно, только операции ввода-вывода выполняются одновременно, поскольку они асинхронны.
Таким образом, рабочие потоки не очень полезны для операций с интенсивным вводом-выводом, поскольку асинхронные операции ввода-вывода более эффективны, чем рабочие.Основная роль рабочих потоков заключается в повышении производительности операций с интенсивным использованием ЦП.
Другие варианты
Кроме того, уже существует множество решений для операций с интенсивным использованием ЦП, таких как многопроцессорные решения (кластерный API), которые обеспечивают полное использование многоядерных ЦП.
Преимущество этой схемы в том, что процессы независимы друг от друга, если у процесса есть проблема, она не повлияет на другие процессы. Кроме того, у них есть стабильные API, однако это также означает, что пространство памяти не может быть общим, а межпроцессное взаимодействие может осуществляться только через данные в формате JSON.
JavaScript и Node.js не поддерживают многопоточность по следующим причинам:
Таким образом, можно подумать, что добавление базового модуля Node.js, который создает и синхронизирует потоки, решит потребность в операциях, интенсивно использующих ЦП.
Однако если вы добавите модуль многопоточности, это изменит характеристики самого языка. Невозможно добавить многопоточные модули в качестве доступных классов или функций. В некоторых языках, поддерживающих многопоточность, таких как Java, функции синхронизации используются для обеспечения синхронизации между несколькими потоками.
А некоторые числовые типы недостаточно атомарны, а это значит, что если вы не оперируете ими синхронно, значение переменной может непрерывно изменяться в случае одновременного выполнения вычислений несколькими потоками, без определенного значения, значение переменная может пройти через несколько байтов после вычисления потоком, а остальные байты данных изменены после вычисления другим потоком. Например, некоторые простые вычисления, такие как 0,1 + 0,2 в JavaScript, имеют в результате 17 знаков после запятой (наибольшее количество знаков после запятой).
var x = 0.1 + 0.2; // x will be 0.30000000000000004
Но вычисления с плавающей запятой не на 100% точны. Поэтому, если вычисления не синхронизированы, у дробного числа никогда не будет точного числа из-за нескольких потоков.
Лучшие практики
Таким образом, для решения проблемы производительности операций с интенсивным использованием ЦП необходимо использовать рабочие потоки. В браузерах уже давно есть функция Workers.
Node.js в одном потоке:
- процесс
- Тема
- цикл событий
- Экземпляр JS-движка
- Экземпляр Node.js
Node.js под многопоточными рабочими имеет:
- процесс
- несколько потоков
- Каждый поток имеет свой собственный цикл обработки событий.
- Каждый поток владеет экземпляром JS-движка.
- Каждый поток владеет экземпляром Node.js
Как на картинке ниже:
Модуль Worker_threads позволяет использовать несколько потоков для одновременного выполнения кода JavaScript. Импортируйте таким образом:
const worker = require('worker_threads');
Рабочие потоки были добавлены в Node.js версии 10, но все еще являются экспериментальными.
Используя рабочие потоки, мы можем иметь несколько экземпляров Node.js в одном процессе, и поток может быть завершен без завершения родительского процесса, он может быть завершен в любое время. Когда рабочий поток уничтожается, ресурсы, выделенные для рабочего потока, все еще не освобождаются. Мы хотим, чтобы эти выделенные ресурсы были встроены в Node.js, давая Node.js возможность создавать потоки и создавать новый экземпляр Node.js в потоке, по сути, как запуск нескольких независимых узлов в одном процессе маршрутизации.
Рабочие потоки имеют следующие особенности:
-
ArrayBuffers
Переменные в памяти можно передавать из одного потока в другой -
SharedArrayBuffer
Переменные в памяти могут совместно использоваться несколькими потоками, но ограничиваются данными в двоичном формате. -
可用的原子操作
, что позволяет более эффективно выполнять определенные операции одновременно и реализовывать переменные гонки -
消息端口
, для связи между несколькими потоками. Может использоваться для передачи структурированных данных между несколькими потоками, объемом памяти -
消息通道
Как асинхронный двусторонний канал связи между несколькими потоками. -
WorkerData
используется для передачи данных запуска. При использовании postMessgae для передачи между несколькими потоками данные будут клонированы, а клонированные данные будут переданы конструктору потока.
API:
-
const { worker, parantPort } = require('worker_threads');
=>worker
Функция эквивалентна независимому потоку среды выполнения JavaScript, а parentPort является экземпляром порта сообщений. -
new Worker(filename)
ornew Worker(code, { eval: true })
=> Есть два способа запустить воркер.Вы можете передать путь к файлу или код.Метод пути к файлу рекомендуется в производственной среде. -
worker.on('message')
,worker.postMessage(data)
=> Вот как несколько потоков прослушивают события и отправляют данные. -
parentPort.on('message')
,parentPort.postMessage(data)
=> использовать в темеparentPort.postMessage
Данные, переданные путем, могут использоваться в родительском процессе.worker.on('message')
полученный в родительском процессе с использованиемworker.postMessage()
Способ использования переданных данных в потокеparentPort.on('message')
способ мониторить.
пример
const { Worker } = require('worker_threads');
const worker = new Worker(`
const { parentPort } = require('worker_threads');
parentPort.once('message',
message => parentPort.postMessage({ pong: message }));
`, { eval: true });
worker.on('message', message => console.log(message));
worker.postMessage('ping');
$ node --experimental-worker test.js
{ pong: ‘ping’ }
Что делает приведенный выше пример, так это использует new Worker для создания потока, и код в потоке слушаетparentPort
Сообщение, и когда данные получены, обратный вызов запускается только один раз, и полученные данные передаются обратно родительскому процессу.
вам нужно использовать--experimental-worker
Запускаем программу, т.к. рабочие еще экспериментальные.
другой пример:
const {
Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
if (isMainThread) {
module.exports = function parseJSAsync(script) {
return new Promise((resolve, reject) => {
const worker = new Worker(filename, {
workerData: script
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
};
} else {
const { parse } = require('some-js-parsing-library');
const script = workerData;
parentPort.postMessage(parse(script));
}
В приведенном выше коде:
-
Worker
: эквивалентно отдельному запущенному потоку JavaScirpt. -
isMainThread
: если true, код не выполняется в рабочем потоке. -
parentPort
: порт сообщений используется для межпотоковой связи. -
workerData
: данные клона передаются конструктору рабочего процесса.
В реальных условиях следует использовать метод пула потоков, иначе затраты на постоянное создание рабочих потоков перевесят преимущества, которые он приносит.
Рекомендации по использованию Workers:
- Передача собственных дескрипторов, таких как сокеты, http-запросы
- Обнаружение взаимоблокировки. Взаимная блокировка — это ситуация, когда несколько процессов заблокированы, потому что каждый процесс удерживает часть ресурса и ожидает, пока другой процесс освободит удерживаемый им ресурс. Обнаружение взаимоблокировок — очень полезная функция в Workers Threads.
- Лучшая изоляция, поэтому, если затронут один поток, это не повлияет на другие потоки.
Некоторые плохие идеи для рабочих:
- Не думайте, что Workers принесут невероятные улучшения скорости, иногда лучше использовать пул потоков.
- Не используйте Workers для параллельного выполнения операций ввода-вывода.
- Не думайте, что накладные расходы на создание рабочего процесса невелики.
наконец
Chrome devTools поддерживает функцию многопоточности Workers в Node.js.worker_threads
Это экспериментальный модуль. Если вам нужно выполнять операции с интенсивным использованием ЦП в Node.js, не рекомендуется использовать рабочие потоки в производственной среде. Вместо этого вы можете использовать пулы процессов.
Подпишитесь на официальный аккаунт [IVWEB Community], чтобы получать свежие статьи каждую неделю, ведущие к вершине жизни!