Ответ
Worker Threads — это механизм в Node.js для выполнения JavaScript-кода в параллельных потоках. Они предназначены для выгрузки CPU-интенсивных (блокирующих) задач из основного потока Event Loop, чтобы не блокировать обработку входящих запросов и асинхронных операций I/O.
Ключевые отличия от кластеризации (cluster):
- Кластер (
cluster): Создаёт несколько независимых процессов (инстансов Node.js), каждый со своей памятью. Подходит для масштабирования сетевых приложений, так как процессы не разделяют память и общаются через IPC. - Worker Threads: Создаёт несколько потоков в рамках одного процесса. Потоки могут (опционально) разделять память через
SharedArrayBuffer, что делает их более подходящими для параллельных вычислений над общими данными.
Основные концепции и API:
Worker: Класс для создания нового потока.parentPort: Канал для обмена сообщениями между главным потоком и воркером.workerData: Позволяет передать начальные данные в воркер при его создании.MessageChannel: Создаёт канал для прямого общения между воркерами.
Пример: вычисление числа Фибоначчи в отдельном потоке
main.js (главный поток):
const { Worker } = require('worker_threads');
const path = require('path');
function runFibonacciService(n) {
return new Promise((resolve, reject) => {
// Создаём воркер и передаём ему данные
const worker = new Worker(path.join(__dirname, 'fibonacci-worker.js'), {
workerData: n
});
worker.on('message', resolve); // Получаем результат
worker.on('error', reject); // Обрабатываем ошибки в воркере
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
}
// Использование
(async () => {
console.log('Main thread: starting heavy calculation...');
const result = await runFibonacciService(40); // Блокирующая операция
console.log(`Main thread: result = ${result}`);
// Event Loop в основном потоке не был заблокирован!
})();
fibonacci-worker.js (код воркера):
const { parentPort, workerData } = require('worker_threads');
// Рекурсивная функция, тяжелая для CPU
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Выполняем вычисление и отправляем результат обратно в главный поток
const result = fibonacci(workerData);
parentPort.postMessage(result);
Когда использовать Worker Threads:
- Обработка изображений/видео.
- Синтаксический анализ или сжатие больших объёмов данных.
- Сложные математические вычисления, машинное обучение.
- Любые синхронные задачи, которые занимают более 10-30 мс и блокируют Event Loop.
Важно: Worker Threads не предназначены для увеличения производительности I/O-операций (работа с файлами, сетью). Для этого эффективнее использовать асинхронные API самого Node.js.
Ответ 18+ 🔞
Давай разберемся с этими Worker Threads, а то мозг уже плавится, как сырок в микроволновке. Представь себе, что твой основной поток Event Loop — это одинокий официант в дохуя популярной забегаловке. Клиенты (запросы) лезут со всех сторон, а он один, и если какому-то гостю взбредет в голову заказать не бургер, а, например, вручную посчитать число Пи до миллиардного знака, то все остальные просто сдохнут с голоду, пока он там цифры складывает. Вот это и есть блокирующая CPU-задача. Пиздец, а не обслуживание.
Так вот, Worker Threads — это нанятые на подхват дополнительные официанты (потоки), которые работают внутри того же самого ресторана (процесса). Ты этого сумасшедшего математика отправляешь на кухню к отдельному повару-воркеру, а твой главный поток продолжает разносить салатики и принимать новые заказы. И все довольны.
Чем это не является, ёпта?
Не путай это с кластеризацией (cluster). Это как будто ты открыл не кухню в том же ресторане, а целую сеть филиалов (отдельные процессы). У каждого свой зал, своя кухня, своя касса. Общаются они между собой только курьерами (IPC). Это круто для масштабирования, когда у тебя тупо много клиентов, но если тебе нужно быстро перетереть одну тонну картошки на всех — не очень удобно, картошку по филиалам возить.
А Worker Threads — это когда у тебя в одном большом ресторане несколько кухонь, и повара могут, если надо, делить одну гигантскую тушу мяса (SharedArrayBuffer). Для параллельных вычислений — то, что доктор прописал.
Из чего это всё собрано, эта конструкция:
Worker: Собственно, сам новый поток. Нанимаешь сотрудника.parentPort: Та самая трубка на кухню, чтобы официант мог крикнуть "Заказ готов!" или "Повар, ты чего там, обосрался что ли?".workerData: Ты можешь сразу при найме воркера сунуть ему в карман записку с заданием. "Вот тебе число 40, иди посчитай Фибоначчи, не отвлекайся".MessageChannel: Это если твои два повара-воркера хотят пообщаться между собой напрямую, без главного официанта. Типа "Дай сольку, братан".
Пример: как отправить считать Фибоначчи, чтобы не ебать основной поток
main.js (главный поток, он же забегаловка):
const { Worker } = require('worker_threads');
const path = require('path');
function runFibonacciService(n) {
return new Promise((resolve, reject) => {
// Наняли нового повара, дали ему инструкцию (файл worker-а)
const worker = new Worker(path.join(__dirname, 'fibonacci-worker.js'), {
workerData: n // И сунули в карман бумажку "n=40"
});
worker.on('message', resolve); // Услышали с кухни "Готово!"
worker.on('error', reject); // Услышали "Ой, всё! Кастрюля взорвалась!"
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Повар сбежал с криком ${code}, всё пропало!`));
}
});
});
}
// Использование
(async () => {
console.log('Главный поток: Ща тут тяжёлую хуйню будем считать...');
const result = await runFibonacciService(40); // Отправили на кухню
console.log(`Главный поток: Всё, принесли! Результат = ${result}`);
// И главное — Event Loop тут не лёг костьми, он продолжал работать!
})();
fibonacci-worker.js (повар на кухне-воркере):
const { parentPort, workerData } = require('worker_threads');
// Сама рекурсивная функция, которая жрёт процессор как не в себя
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Достаём из кармана задание, пашем, и кричим в трубку результат
const result = fibonacci(workerData);
parentPort.postMessage(result);
Когда это вот всё нужно применять на практике?
- Обработка изображений/видео. Когда нужно скормить фото нейросети, а она будет думать овердохуища.
- Сжатие или парсинг гигабайтов логов. Чтоб не зависнуть на полчаса.
- Сложная математика, машинное обучение. Всякие матрицы, графы, хуи-моржовые.
- Любые синхронные задачи, которые тупо блокируют Event Loop больше чем на 10-30 мс. Если твоя операция может заставить всех клиентов ждать — это верный знак, что её надо выпихивать в воркер.
Важный момент, блядь, запомни: Worker Threads — это НЕ волшебная таблетка для I/O. Не для чтения файлов, не для сетевых запросов. Для этого в Node.js и так есть асинхронные API, которые справляются на ура. Воркеры нужны именно когда процессор упирается лбом в стенку и начинает тупить. Всё, теперь ты понял, да? Иди делай.