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.
Führt vertrauenswürdige eigenständige Funktionen auf begrenzten Workern mit Timeout-Erholung aus. Funktionsquellcode kann keine Closures übernehmen und darf nie unvertrauenswürdig sein. Vorinstallierte Handler-Registries und Transferables sind mögliche Erweiterungen.
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.executeMethodPlant eine vertrauenswürdige eigenständige Funktion mit Argumenten ein.WorkerPoolOptionsLegt Queue-Kapazität und Task-Timeout fest.destroyLehnt offene Promises ab und wartet Worker-Ende ab.05 · Selbst bauen
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 solltest du testen?
- 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.
Selbst ausprobieren
Formuliere die erste Prüfaussage als fehlschlagenden Test, implementiere die kleinste Korrektur und ergänze einen Grenzfall.
0, false, leerer String und undefined beenden ihre Task ohne Hänger.
Ansatz vergleichen
Dein Test sollte beim gezielten Verletzen der Invariante fehlschlagen und nach der Korrektur wieder bestehen. Halte den Aufbau deterministisch und prüfe ein beobachtbares Ergebnis.
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.
Funktionsquellcode kann keine Closures übernehmen und darf nie unvertrauenswürdig sein. Vorinstallierte Handler-Registries und Transferables sind mögliche Erweiterungen.
08 · Weiterdenken
Quellcode und verwandte Ideen
yarn tsx packages/worker-pool/examples/basic.ts