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.
Что почитать дальше
- Процессы и cluster - масштабирование без общей памяти.
- Event-loop lag - как доказать, что worker действительно нужен.