Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

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().

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.