01 · Problem
Wofür braucht man das?
CPU-intensive Funktionen blockieren den Event Loop; pro Aufgabe einen neuen Thread zu starten ist jedoch teuer und braucht keine kontrollierte Warteschlange.
02 · Denkmodell
Das mentale Modell
Der Main Thread sendet nur Daten plus Task-ID. Worker besitzen vorinstallierte Handler, führen einen Auftrag aus und antworten mit Resultat oder serialisiertem Fehler.
WorkerPool nutzt worker_threads, initialisiert Worker doppelt und versucht Funktionen per postMessage zu klonen, was DataCloneError auslöst. Falsy Resultate lösen Promises nicht auf, Worker gelten gleichzeitig als frei und beschäftigt, und destroy beendet Wartende nicht vollständig.
03 · Kontrollfluss
Was passiert in welcher Reihenfolge?
- Worker einmal starten und auf ready warten.
- Task-ID, registrierten Handlernamen und klonbare Payload in eine FIFO-Queue legen.
- Nur freie Worker belegen und genau eine laufende Task pro Worker zuordnen.
- Bei result oder error Promise beenden, Zuordnung löschen und nächsten Auftrag starten.
- Bei Exit laufende Task ablehnen und Worker je nach Shutdown-Status ersetzen.
04 · Bauteile
Die entscheidenden Verträge
WorkerPool
Experimenteller Pool auf Node.js worker_threads.
runTask
Soll Arbeit einreihen und das Workerresultat zurückliefern.
destroy
Beendet Worker; Queue- und Promise-Cleanup sind derzeit lückenhaft.
05 · Build it yourself
Selbst implementieren
Sende keine Funktionen. Der Worker lädt ein festes Registry-Modul und akzeptiert nur Taskname plus strukturklonbare Daten.
- Gemeinsames Message-Protokoll mit id, task, payload, result und error definieren.
- Worker-seitig erlaubte Handler in einer Registry auflösen.
- Manager-seitig pro ID resolve/reject halten und Worker erst nach Antwort freigeben.
// worker.ts
const handlers = {
square: (value: number) => value * value,
};
parentPort?.on("message", async ({ id, task, payload }) => {
try {
const result = await handlers[task as keyof typeof handlers](payload);
parentPort?.postMessage({ id, ok: true, result });
} catch (error) {
parentPort?.postMessage({ id, ok: false, error: String(error) });
}
});06 · Verifizieren
Was du testen solltest
- 0, false, leerer String und undefined beenden ihre Task ohne Hänger.
- Ein Worker verarbeitet niemals zwei Aufgaben gleichzeitig und wird nach Fehler wieder frei.
- Crash und destroy lehnen jede betroffene laufende oder wartende Task genau einmal ab.
07 · Grenzen
Kompromisse und Stolperfallen
- Langlebige Worker amortisieren Startkosten, reservieren aber dauerhaft Speicher.
- Eine Handler-Registry ist sicher und serialisierbar, aber weniger dynamisch als Funktionsübergabe.
- Große Payloads kosten Structured-Clone-Zeit; Transferables können Kopien vermeiden.
Ein Worker-Pool braucht Backpressure. Begrenze Queue-Länge oder dokumentiere klar, wann neue Aufgaben abgewiesen werden.
08 · Weiterdenken