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
postMessageun obiect cu funcții sau o instanță de clasă: funcțiile dauDataCloneError, 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.