worker_threads (Worker) în Node.js

Sistem, procese și fire Node.js 10.5 (stabil din 12.11)

Rulează cod JavaScript în fire de execuție separate, ca un calcul greu să nu blocheze restul programului.

Sintaxă
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
const w = new Worker(fișier, { workerData });
w.on("message", ...);  parentPort.postMessage(valoare);

Ce face worker_threads (Worker)

Node.js rulează codul tău pe un singur fir. Operațiile de intrare-ieșire (fișiere, rețea) nu îl blochează, dar un calcul lung (procesarea unei imagini, un hash pe date mari) oprește tot: un server nu mai răspunde nimănui. worker_threads mută astfel de calcule pe alt fir, cu propriul lui motor JavaScript.

Firul principal creează un new Worker(fișier, { workerData }). În worker, workerData conține datele primite, iar parentPort.postMessage() trimite rezultatul înapoi. Firul principal îl primește prin evenimentul "message". Datele sunt copiate între fire (algoritmul structuredClone); funcțiile nu pot fi trimise.

Un fișier poate fi și principal, și worker: isMainThread spune în care rol rulează. Pentru operații de I/O nu ai nevoie de fire, Node.js le face deja asincron. Pornirea unui worker costă: pentru multe sarcini mici se folosește un grup fix de fire (pool).

Parametri

Parametru Descriere
new Worker(fișier, opțiuni) Pornește un fir care rulează fișierul (cale sau URL).
opțiuni.workerData Date copiate în worker, citite cu workerData.
parentPort.postMessage(v) Trimite o valoare firului principal.
worker.terminate() Oprește firul.

Exemple

Exemplul 1: Un calcul greu pe alt fir

Același fișier joacă ambele roluri. Firul principal pornește doi workeri, fiecare numără numerele prime dintr-un interval, apoi aduce rezultatele cu Promise.all.

import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";

function numaraPrime(de, pana) {
  let total = 0;
  for (let n = Math.max(2, de); n < pana; n++) {
    let prim = true;
    for (let d = 2; d * d <= n; d++) if (n % d === 0) { prim = false; break; }
    if (prim) total++;
  }
  return total;
}

if (isMainThread) {
  const porneste = (de, pana) => new Promise((rezolva, respinge) => {
    const w = new Worker(new URL(import.meta.url), { workerData: { de, pana } });
    w.once("message", rezolva);
    w.once("error", respinge);
  });
  const [a, b] = await Promise.all([porneste(0, 100_000), porneste(100_000, 200_000)]);
  console.log("Prime sub 100.000:", a);
  console.log("Prime între 100.000 și 200.000:", b);
} else {
  parentPort.postMessage(numaraPrime(workerData.de, workerData.pana));
}

Rezultat

Prime sub 100.000: 9592
Prime între 100.000 și 200.000: 8392

Exemplul 2: Mesaje în ambele sensuri

Worker-ul este scris ca text și pornit cu eval: true, doar ca exemplul să stea într-un singur fișier. Worker-ul se închide singur după ce firul principal închide portul.

import { Worker } from "node:worker_threads";

const cod = `
  const { parentPort } = require("node:worker_threads");
  parentPort.on("message", (comanda) => {
    const total = comanda.produse.reduce((s, p) => s + p.pret, 0);
    parentPort.postMessage({ id: comanda.id, total });
  });
`;
const w = new Worker(cod, { eval: true });

w.on("message", (r) => {
  console.log(`Comanda ${r.id}: ${r.total} lei`);
  if (r.id === 2) w.terminate();
});
w.postMessage({ id: 1, produse: [{ pret: 20 }, { pret: 15.5 }] });
w.postMessage({ id: 2, produse: [{ pret: 100 }] });

Rezultat

Comanda 1: 35.5 lei
Comanda 2: 100 lei

Greșeli frecvente

  • Folosești workeri pentru citiri de fișiere sau cereri HTTP: acestea nu blochează firul principal, iar workerii doar adaugă costul pornirii. Workerii sunt pentru calcul.
  • Trimiți prin postMessage un obiect cu funcții sau o instanță de clasă: funcțiile dau DataCloneError, iar clasele ajung ca obiecte simple, fără metode.
  • Pornești un worker nou pentru fiecare cerere: pornirea durează și consumă memorie. Ține un număr fix de workeri și dă-le sarcini pe rând.

Comenzi înrudite

  • child_process.spawn: Pornește un program ca proces copil și citește ieșirea lui pe măsură ce apare (stream), fără shell.
  • os (node:os): Informații despre sistemul de operare: platformă, procesoare, memorie, directorul utilizatorului, sfârșitul de rând.
  • EventEmitter (node:events): Clasa pentru evenimente din Node.js: înregistrezi ascultători cu on și îi declanșezi cu emit. Baza pentru stream-uri și servere.