Worker threads
Поток выполнения (thread) — отдельная линия работы внутри одной программы. Ваш код в Node.js выполняется в одном потоке, и долгое вычисление блокирует цикл событий: сервер перестаёт отвечать. Модуль node:worker_threads запускает код в дополнительном потоке — воркере (worker). Он считает параллельно, а главный поток тем временем обслуживает запросы.
Воркер в одном файле
Файл может быть и главной программой, и воркером одновременно — их различает флаг isMainThread:
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
function countWords(text) {
return text.split(/\s+/).filter(Boolean).length;
}
if (isMainThread) {
const worker = new Worker(new URL(import.meta.url), {
workerData: 'Привет из главного потока блога',
});
worker.on('message', (count) => console.log(`Слов: ${count}`));
} else {
parentPort.postMessage(countWords(workerData));
}Выведется «Слов: 5». Главный поток создаёт воркер из того же файла (import.meta.url — его адрес) и передаёт данные в workerData. Воркер считает и отправляет результат через parentPort.postMessage().
Как они общаются
У потоков нет общих переменных. Они обмениваются сообщениями — данные при этом копируются:
| Где | Что использовать |
|---|---|
| Главный поток → воркер при создании | workerData |
| Воркер → главный поток | parentPort.postMessage(data) |
| Главный поток получает | worker.on('message', fn) |
| Ошибка в воркере | worker.on('error', fn) |
Отдельный файл воркера
В проекте воркер обычно лежит в своём файле. Создайте src/workers/stats.js — он считает статистику по всем статьям:
// src/workers/stats.js
import { parentPort, workerData } from 'node:worker_threads';
const words = workerData.posts
.map((post) => post.body.split(/\s+/).filter(Boolean).length)
.reduce((sum, n) => sum + n, 0);
parentPort.postMessage({ posts: workerData.posts.length, words });Главный поток оборачивает запуск в промис, чтобы ждать результат через await:
// src/stats.js
import { Worker } from 'node:worker_threads';
export function calculateStats(posts) {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL('./workers/stats.js', import.meta.url), {
workerData: { posts },
});
worker.once('message', resolve);
worker.once('error', reject);
});
}В маршруте /stats сервера вызовите await calculateStats(await loadPosts()) — подсчёт пойдёт в отдельном потоке, и остальные посетители не заметят задержки.
Когда нужен воркер
Воркеры нужны для работы процессора: сжатие картинок, разбор огромного JSON, сложные расчёты. Для чтения файлов и сетевых запросов они не нужны — это и так не блокирует цикл событий.
Частая ошибка
Воркер получает копию данных, а не ссылку на них. Если изменить объект внутри воркера, в главном потоке он останется прежним:
// src/workers/stats.js
workerData.posts.push({ title: 'Новая' }); // главный поток этого не увидитРезультат работы всегда возвращайте сообщением через parentPort.postMessage().