← назад к разделу

Worker запускает отдельный экземпляр V8 в том же процессе. У него своя память JavaScript и свой event loop, поэтому тяжелый расчет не останавливает HTTP-обработчики основного потока.

Когда worker нужен

Признак - длительное синхронное вычисление: анализ большого массива, ресайз изображения, сложная сериализация. Обычный сетевой запрос или запрос к базе в worker не нужен.

import { Worker } from 'node:worker_threads';

const worker = new Worker(new URL('./sum-worker.js', import.meta.url), {
  workerData: { values: [1, 2, 3, 4] },
});

const total = await new Promise<number>((resolve, reject) => {
  worker.once('message', resolve);
  worker.once('error', reject);
});

workerData и сообщения копируются через structured clone. Это не общий объект: изменение копии в worker не меняет исходный массив.

Передача и разделение памяти

Transferable-объекты можно передать без копирования через transferList, но прежний владелец теряет доступ к ним. Для настоящей общей памяти используют SharedArrayBuffer, а согласование - Atomics.

Общий буфер не делает соседние операции атомарными. Два workers, которые одновременно читают и изменяют слово без Atomics, получат гонку, как в других языках.

Worker - ресурс с жизненным циклом

Сервер не должен создавать worker на каждый запрос бесконечно. Пул ограничивает число workers, а при остановке приложения каждый получает сигнал завершения и затем terminate при необходимости.

await worker.terminate();

terminate не ждет конца текущего расчета. Если результат важен, сначала реализуйте кооперативную отмену через сообщение и дождитесь события exit.

Коротко

  • Worker изолирует CPU-расчет от основного event loop.
  • Сообщения копируют данные, transferList передает владение.
  • SharedArrayBuffer требует синхронизации через Atomics.
  • Количество workers ограничивают, завершение проектируют заранее.
  • Пул libuv не заменяет worker для вычислений на JavaScript.

Что почитать дальше