12k
All articles

Der Event Loop, Worker Threads und Nebenläufigkeit in Node.js

Node.js-Event-Loop, libuv-Thread-Pool, Worker Threads und Cluster klar erklärt mit Praxisratgeber für I/O- und CPU-Last.

OpenReplay Team
OpenReplay Team
Der Event Loop, Worker Threads und Nebenläufigkeit in Node.js

Node.js führt Ihr JavaScript auf einem einzigen Thread aus, der vom Event Loop gesteuert wird. Nebenläufigkeit entsteht durch das Auslagern von Wartezeiten — libuv übergibt blockierende I/O-Operationen an einen Hintergrund-Thread-Pool — nicht durch parallele Ausführung Ihres JavaScripts. Wenn Sie JavaScript für CPU-intensive Aufgaben parallel ausführen müssen, kommt ein separater Mechanismus zum Einsatz: Worker Threads, von denen jeder ein vollständiges V8-Isolat mit eigenem Event Loop ist. Diese drei Konzepte — der einzelne JS-Thread, der libuv-Thread-Pool und Worker Threads — werden ständig miteinander verwechselt, und genau diese Verwirrung ist die Ursache für langsame Endpunkte und blockierte Server.

Dieser Artikel trennt die Schichten präzise voneinander. Er erläutert die Phasen des Event Loops und die Aufteilung in Microtasks und Macrotasks, wofür der libuv-Thread-Pool tatsächlich zuständig ist (und wofür nicht), wann Worker Threads gegenüber einfachem async/await überlegen sind, wie sich Clustering davon unterscheidet, und bietet eine Entscheidungsregel für die Wahl zwischen diesen Ansätzen. Der Code ist für Node.js 24 (Active LTS) geschrieben, mit Node.js 26 als Current-Release; Worker Threads sind stabil, nicht experimentell.

Wichtige Erkenntnisse

  • Node führt Ihr JavaScript auf einem Thread aus; der Event Loop erreicht Nebenläufigkeit, indem er blockierende I/O an den Hintergrund-Thread-Pool von libuv übergibt — nicht durch parallele Ausführung Ihres Codes.
  • Der libuv-Thread-Pool hat standardmäßig 4 Threads und kann über UV_THREADPOOL_SIZE auf maximal 1024 erhöht werden; er bedient Dateisystem-, dns.lookup-, Krypto- und zlib-Operationen — jedoch nicht Netzwerk-Sockets, die direkt das epoll/kqueue/IOCP des Betriebssystems verwenden.
  • Ein Worker Thread ist nicht „nur ein OS-Thread” — jeder ist ein separates V8-Isolat mit eigenem Event Loop und eigenem libuv-Loop, weshalb Worker keine gewöhnlichen Objekte teilen können und über Message Passing kommunizieren müssen.
  • Microtasks sind keine Phase des Event Loops: Zuerst werden process.nextTick-Callbacks abgearbeitet, dann die Promise-Microtask-Queue, und erst dann rückt der Loop zur nächsten Macrotask vor.
  • Entscheiden Sie nach dem Engpass: async/await für I/O-gebundene Arbeit, Worker Threads für CPU-gebundenes JavaScript und Cluster, um I/O-gebundene Last über mehrere Kerne zu skalieren.

Das Kernmodell: ein JS-Thread, ein Event Loop und libuv

Node.js führt Ihr JavaScript auf einem einzigen Thread aus, und dieser Thread betreibt den Event Loop. Die Laufzeitumgebung basiert auf Googles V8-Engine zur JavaScript-Ausführung und auf libuv, einer C-Bibliothek, die den Event Loop und asynchrone I/O bereitstellt. Der Trick, der es einem einzigen Thread ermöglicht, Tausende gleichzeitiger Verbindungen zu verwalten, ist Delegation: Wenn Ihr Code eine blockierende Operation wie einen Datei-Lesevorgang aufruft, wartet Node nicht. Es registriert die Operation bei libuv, kehrt sofort zurück, und Ihr Callback wird später ausgeführt, wenn das Ergebnis bereitsteht.

Nebenläufigkeit in Node bedeutet das Auslagern von Wartezeiten. Während ein Datei-Lesevorgang oder ein DNS-Lookup ausstehend ist, steht der einzelne JS-Thread frei zur Verfügung, um andere Callbacks auszuführen. Nichts in Ihrem JavaScript läuft parallel — es gibt genau einen Call Stack — aber viele Operationen können gleichzeitig in Bearbeitung sein, weil die langsamen Teile anderswo stattfinden.

Deshalb ist „Node ist single-threaded” eine Halbwahrheit, die einer Korrektur bedarf. Die JavaScript-Ausführung ist single-threaded. Die Laufzeitumgebung ist es nicht: libuv verwaltet einen Pool von Hintergrund-Threads, und das Betriebssystem verarbeitet Netzwerk-Sockets im Auftrag von Node. Betrachten Sie „single-threaded” als Aussage darüber, wo Ihr Code ausgeführt wird — nicht über den Prozess als Ganzes.

Verbreiteter Irrtum: „Node ist single-threaded.” Ihr JavaScript läuft auf einem Thread; der Node-Prozess verwendet jedoch mehrere. Diese Unterscheidung ist der eigentliche Kern dieses Artikels.

Nebenläufigkeit vs. Parallelismus — präzise definiert

Nebenläufigkeit bedeutet, dass mehrere Aufgaben im selben Zeitraum Fortschritte erzielen, indem sie sich eine gemeinsame Ressource durch Verschachtelung teilen; Parallelismus bedeutet, dass mehrere Aufgaben im selben Moment auf separaten Kernen ausgeführt werden. Ein Single-Core-Rechner, der Node ausführt, ist nebenläufig, aber nicht parallel: Der Event Loop wechselt schnell zwischen laufenden Operationen, aber nur ein Stück JavaScript wird zu einem bestimmten Zeitpunkt ausgeführt. Worker Threads und Clustering fügen echten Parallelismus hinzu, indem sie zusätzliche Ausführungskontexte einführen, die das Betriebssystem auf verschiedenen Kernen einplanen kann.

Die praktische Konsequenz: Nebenläufigkeit löst Warte-Probleme (I/O), Parallelismus löst Berechnungs-Probleme (CPU). Den falschen Ansatz zu wählen ist die Hauptursache für die meisten Performance-Fehler in Node.

Die sechs Phasen des Event Loops und die Microtask-Aufteilung

Der Event Loop läuft in einem festen Zyklus aus sechs Phasen, und jede Phase hat ihre eigene Queue von Callbacks, die vollständig abgearbeitet wird, bevor zur nächsten Phase übergegangen wird. Gemäß dem offiziellen Node.js Event Loop Guide sind die Phasen in dieser Reihenfolge:

  1. Timers — führt Callbacks aus, die von setTimeout() und setInterval() geplant wurden und deren Schwellenwert abgelaufen ist.
  2. Pending callbacks — führt bestimmte Callbacks auf Systemebene aus, die aus einem vorherigen Zyklus zurückgestellt wurden.
  3. Idle, prepare — nur für interne Verwendung.
  4. Poll — ruft neue I/O-Ereignisse ab und führt deren Callbacks aus; der Loop blockiert hier auf I/O, wenn es sonst nichts zu tun gibt.
  5. Check — führt setImmediate()-Callbacks aus.
  6. Close callbacks — führt Close-Handler wie socket.on('close', ...) aus.

Beachten Sie die dritte Phase: Viele Erklärungen listen nur fünf Phasen auf und lassen idle/prepare aus, die zwar existiert, aber für interne libuv-Verwaltung reserviert ist. Sie ist real; Sie können jedoch nie direkt in sie einplanen.

Microtasks sind keine Phase

Microtasks sind keine Phase des Event Loops. Zuerst werden process.nextTick-Callbacks abgearbeitet, dann die Promise-Microtask-Queue, und erst dann rückt der Loop zur nächsten Macrotask vor — daher hat process.nextTick Vorrang vor Promise.then, welches wiederum Vorrang vor setTimeout hat. Der Node-Guide stellt explizit klar, dass process.nextTick technisch gesehen nicht Teil des Event Loops ist; seine Queue wird nach Abschluss der aktuellen Operation verarbeitet, unabhängig von der aktuellen Phase, und die Promise-Queue folgt danach — beide bevor der Loop voranschreitet.

Das ergibt eine klare dreistufige Priorität: process.nextTick → Promise-Microtasks → Macrotasks (Timers, I/O, setImmediate).

Eine konkrete Reihenfolge-Demo

Die klassische Verwirrung ist setImmediate vs. setTimeout(0). Innerhalb eines I/O-Callbacks ist die Reihenfolge deterministisch; auf oberster Ebene ist sie es nicht.

// Run on Node.js 24.16.0
const fs = require('node:fs');

fs.readFile(__filename, () => {
  setTimeout(() => console.log('1: setTimeout(0)'), 0);
  setImmediate(() => console.log('2: setImmediate'));
  Promise.resolve().then(() => console.log('3: promise'));
  process.nextTick(() => console.log('4: nextTick'));
});

Ausgabe:

4: nextTick
3: promise
2: setImmediate
1: setTimeout(0)

nextTick und das Promise werden beide abgearbeitet, bevor der Loop voranschreitet, wobei nextTick zuerst kommt. Da die Callbacks dann innerhalb eines I/O-Zyklus (der Poll-Phase) geplant wurden, trifft der Loop als nächstes auf die Check-Phase, sodass setImmediate ausgelöst wird, bevor der Loop zur Timers-Phase zurückkehrt. Der Node-Guide bestätigt, dass setImmediate() immer vor einem Timer ausgeführt wird, wenn beide innerhalb eines I/O-Zyklus geplant werden. Werden dieselben beiden auf oberster Ebene geplant, ist die Reihenfolge nicht deterministisch — verlassen Sie sich außerhalb eines I/O-Callbacks nicht darauf.

Der libuv-Thread-Pool: Standard 4, Maximum 1024 — und was ihn tatsächlich nutzt

Der libuv-Thread-Pool ist ein fester Satz von Hintergrund-Threads — standardmäßig 4, erweiterbar auf maximal 1024 — den libuv verwendet, um Operationen auszuführen, für die es keine nicht-blockierende OS-Primitive gibt. Gemäß der libuv-Thread-Pool-Dokumentation wird dieses Maximum über die Umgebungsvariable UV_THREADPOOL_SIZE festgelegt. (Die Obergrenze stieg in libuv 1.30.0 von 128 auf 1024 — ältere Artikel, die „128” zitieren, sind veraltet.) Der Pool wird von allen Event Loops in einem Prozess gemeinsam genutzt.

Was darauf läuft, ist eine spezifische, endliche Liste. Die Node.js-CLI-Dokumentation für UV_THREADPOOL_SIZE nennt die Nutzer: die fs-APIs (außer Watchern und den explizit synchronen Varianten), dns.lookup() sowie die asynchronen Krypto- und zlib-Operationen wie crypto.pbkdf2(), crypto.scrypt(), crypto.randomBytes(), crypto.generateKeyPair() und zlib-Komprimierung.

Das Wichtigste hier ist, was den Pool nicht nutzt. Netzwerk-I/O berührt den Thread-Pool nicht. Wie der offizielle Don’t Block the Event Loop-Guide erläutert, werden Netzwerk-Sockets durch den Polling-Mechanismus des Betriebssystems verwaltet — epoll unter Linux, kqueue unter macOS/BSD, IOCP unter Windows — und erscheinen direkt in der Poll-Phase. Es gibt auch eine DNS-Besonderheit: dns.lookup() (das getaddrinfo aufruft) nutzt den Pool, aber die dns.resolve*()-Familie (die c-ares verwendet) nicht — daher ist die pauschale Aussage „DNS nutzt den Pool” falsch.

# Vor dem Start von Node setzen — der Pool wird bei erster Nutzung vorab allokiert.
UV_THREADPOOL_SIZE=8 node server.js

Eine wichtige Besonderheit im Betrieb: libuv allokiert die maximale Anzahl von Threads bei der ersten Nutzung des Pools vorab, daher muss UV_THREADPOOL_SIZE vor diesem Zeitpunkt gesetzt werden — in der Praxis vor dem Start von Node. Das nachträgliche Ändern von process.env.UV_THREADPOOL_SIZE, nachdem der Pool bereits genutzt wurde, hat keine Wirkung.

Verbreiteter Irrtum:UV_THREADPOOL_SIZE erhöhen, um CPU-Arbeit zu beschleunigen.” Das Erhöhen von UV_THREADPOOL_SIZE beschleunigt gleichzeitige I/O-Operationen, niemals CPU-gebundenes JavaScript — für CPU-Arbeit benötigen Sie einen Worker Thread, da der Pool Ihr JavaScript nicht ausführt. Er führt libuv-Operationen auf C-Ebene aus, nicht Ihre JS-Funktionen.

Worker Threads: echter Parallelismus für CPU-gebundenes JavaScript

Worker Threads führen JavaScript parallel auf separaten Threads aus — aus einem einzigen Grund: für CPU-intensive Arbeit, die andernfalls den einzelnen JS-Thread blockieren würde. Sie helfen nicht bei I/O-gebundener Arbeit, die Node’s eingebaute asynchrone I/O bereits effizienter verarbeitet. Die worker_threads-Dokumentation stellt fest, dass Worker für die Ausführung CPU-intensiver JavaScript-Operationen nützlich sind und bei I/O-intensiver Arbeit wenig helfen, da Node’s eingebaute asynchrone I/O effizienter ist als Worker es sein können.

Der wichtige Punkt, den die meisten Artikel übersehen: Ein Worker Thread ist nicht „nur ein OS-Thread”. Jeder ist ein separates V8-Isolat mit eigenem Event Loop und eigenem libuv-Loop. Diese Isolation ist genau der Grund, warum Worker keine gewöhnlichen JavaScript-Objekte teilen können und warum alles, was Sie per postMessage senden, kopiert wird.

Verbreiteter Irrtum: „Worker sind nur OS-Threads.” Jeder Worker ist ein vollständiges V8-Isolat plus eigenem Event Loop und libuv-Loop, der in einem Thread läuft — deshalb gibt es keine gemeinsamen Globals und keinen gemeinsamen Closure-Scope zwischen Main-Thread und Worker.

Isolation und Message Passing

Worker kommunizieren über Message Passing, und die Nutzlast wird tief kopiert. Die Daten, die Sie über workerData oder postMessage() übergeben, werden gemäß dem HTML Structured Clone Algorithm geklont — Funktionen, Klassen-Prototypen und Live-Referenzen überleben den Transfer nicht. Die einzige Möglichkeit, das Kopieren zu umgehen, ist gemeinsamer Speicher: Worker Threads können Speicher nur über einen SharedArrayBuffer teilen (oder durch Übertragen eines ArrayBuffer, was das Eigentum verschiebt statt zu kopieren). Alles andere wird strukturell geklont.

// main.js — Run on Node.js 24.16.0
const { Worker } = require('node:worker_threads');

const worker = new Worker('./fib-worker.js', { workerData: { n: 42 } });
worker.on('message', (result) => console.log('fib(42) =', result));
worker.on('error', (err) => console.error(err));
// fib-worker.js
const { parentPort, workerData } = require('node:worker_threads');

function fib(n) {
  return n < 2 ? n : fib(n - 1) + fib(n - 2);
}

parentPort.postMessage(fib(workerData.n));

isMainThread ermöglicht es einer einzelnen Datei, zwischen den beiden Rollen zu verzweigen, wenn Sie die Dateien lieber nicht aufteilen möchten. Für die vollständige API-Oberfläche — MessageChannel, MessagePort, Transfer-Listen, receiveMessageOnPort — siehe die worker_threads-Dokumentation. Im Vergleich zu Threads in C++ oder Java tauscht das Modell gemeinsamen Speicher als Standard (und die damit verbundenen Locks, Mutexes und Race Conditions) gegen Isolation als Standard: sicherer, mit Kopieraufwand als Preis.

Einen Pool verwenden, nicht einen Worker pro Aufgabe

Für jede Anfrage einen neuen Worker zu erstellen ist verschwenderisch. Die Node-Dokumentation stellt explizit klar, dass Sie in der Praxis einen Pool von Workern verwenden sollten, da der Overhead beim Erstellen von Workern sonst wahrscheinlich deren Nutzen übersteigen würde. Der Community-Standard-Pool ist piscina. Piscina ist eine schnelle, effiziente Node.js-Worker-Thread-Pool-Implementierung; die aktuelle Version ist 5.2.0. Schreiben Sie 5.x in Ihren package.json-Bereich und pinnen Sie den Point-Release in der CI-Umgebung.

Es gibt eine Folgerung, die viele überrascht: Verschieben Sie keine bereits asynchronen Operationen in einen Worker. Asynchrone Krypto-, fs- und zlib-Operationen laufen bereits auf libuv’s Hintergrund-Threads, sodass das Einwickeln in einen Worker nur einen Thread plant, der einen weiteren Thread plant — ohne Gewinn. Worker lohnen sich nur für synchrones, CPU-gebundenes JavaScript.

Praxisbeispiel: Eine CPU-intensive Route, die alle blockiert

Eine synchrone CPU-Aufgabe auf dem Main-Thread friert den Event Loop für jede laufende Anfrage ein, nicht nur für die, die sie ausgelöst hat. Hier ist das Fehlermuster in einem Express-Handler:

// server-blocking.js — Run on Node.js 24.16.0, express 5.x
const express = require('express');
const app = express();

function fib(n) {
  return n < 2 ? n : fib(n - 1) + fib(n - 2);
}

app.get('/report', (req, res) => {
  res.json({ value: fib(45) }); // blockiert den Event Loop für Sekunden
});

app.get('/health', (req, res) => res.send('ok'));

app.listen(3000);

Während fib(45) läuft, gibt /health keine Antwort zurück — der einzelne Thread ist mit Berechnungen beschäftigt, und jede gleichzeitige Anfrage reiht sich dahinter ein. Die Lösung besteht darin, die CPU-Arbeit in einen Pool auszulagern und auf das Ergebnis zu awaiten, sodass der Event Loop weiterhin Anfragen bedienen kann:

// server-pooled.js — Run on Node.js 24.16.0, express 5.x, piscina 5.2.0
const express = require('express');
const Piscina = require('piscina');
const path = require('node:path');

const pool = new Piscina({ filename: path.resolve(__dirname, 'fib-task.js') });
const app = express();

app.get('/report', async (req, res) => {
  const value = await pool.run({ n: 45 }); // läuft auf einem Worker; Loop bleibt frei
  res.json({ value });
});

app.get('/health', (req, res) => res.send('ok'));

app.listen(3000);
// fib-task.js
module.exports = ({ n }) => {
  const fib = (x) => (x < 2 ? x : fib(x - 1) + fib(x - 2));
  return fib(n);
};

Nun läuft fib(45) auf einem Worker, der Event Loop bleibt reaktionsfähig, und /health antwortet sofort, während die rechenintensive Route im Hintergrund ausgeführt wird.

Ein blockierter Event Loop hat ein charakteristisches Produktionsmuster: Da eine synchrone CPU-Aufgabe den einzelnen JS-Thread für jede laufende Anfrage einfriert, zeigt es sich als gleichzeitiges Stocken vieler Benutzer zum selben Zeitpunkt — nicht als schlechte Verbindung eines einzelnen Benutzers. Dieses Muster korrelierter Einfrierungen ist genau das, was Session-Replay über simultane Sitzungen hinweg sichtbar macht, und Sie können es zur Laufzeit mit monitorEventLoopDelay aus node:perf_hooks bestätigen, wobei ein hoher p99-Wert darauf hinweist, dass der Loop (oder der Thread-Pool) ausgelastet ist.

Clustering und mehrere Prozesse: I/O über Kerne skalieren

Clustering skaliert eine Node-Anwendung über CPU-Kerne, indem mehrere Prozesse geforkt werden, von denen jeder die vollständige Anwendung mit eigenem Event Loop und eigenem Speicher ausführt und sich einen lauschenden Socket teilt. Das Cluster-Modul ist der eingebaute Weg dafür; es ist das richtige Werkzeug, wenn Ihr Engpass I/O-gebundener Durchsatz ist und Sie alle Kerne des Rechners nutzen möchten.

Die Unterscheidung von Worker Threads ist wichtig. Cluster-Worker sind separate Prozesse mit vollständig isoliertem Speicher, die über IPC kommunizieren; Worker Threads sind separate Threads innerhalb eines Prozesses, die Speicher über SharedArrayBuffer teilen können. Clustering dient der Verarbeitung mehr gleichzeitiger Anfragen über Kerne; Worker Threads dienen dazu, CPU-gebundenes JavaScript vom Request-Thread zu entlasten. In der Praxis verwendet ein hochdurchsatzfähiger Dienst oft beides: Cluster (oder einen Prozessmanager / Container-Replikate) zur Verteilung über Kerne und einen Worker-Pool innerhalb jedes Prozesses, um gelegentliche CPU-Spitzen abzufangen.

Ein Entscheidungsleitfaden: async, Pool, Worker oder Cluster

Entscheiden Sie nach Ihrem Engpass: async/await für I/O-gebundene Arbeit, Worker Threads für CPU-gebundenes JavaScript, Cluster (oder mehrere Prozesse) zur Skalierung I/O-gebundener Last über Kerne, und einen Worker-Pool wie piscina, wenn Sie andernfalls Worker-Startkosten bei jeder Anfrage zahlen würden.

WerkzeugFührt JS parallel aus?Teilt Speicher?Am besten fürHauptkosten
async/await + Event LoopNeinN/A (ein Thread)I/O-gebundene Arbeit (Netzwerk, DB, Dateien)Blockiert bei CPU-Arbeit
libuv-Thread-PoolNein (führt C aus, nicht Ihr JS)N/AGleichzeitige fs/dns.lookup/Krypto/zlibFeste Größe; nicht für Ihr JS
Worker Threads (+ Pool)JaNur über SharedArrayBufferCPU-gebundenes JavaScriptStart + Structured-Clone-Kopieraufwand
Cluster / mehrere ProzesseJaNein (nur IPC)Skalierung I/O-gebundener Last über KerneProzess-Overhead; kein gemeinsamer Zustand

Schnelle Regeln:

  • I/O-gebunden und noch nicht parallelisiert? Verwenden Sie async/await. Der Thread-Pool und das Betriebssystem geben Ihnen Nebenläufigkeit bereits kostenlos.
  • Eine Route führt schwere synchrone Berechnungen durch? Lagern Sie sie in einen Worker Thread aus, hinter einem Pool zur Wiederverwendung.
  • Sättigung eines Kerns unter gleichzeitigem Traffic? Verteilen Sie über Cluster (oder führen Sie mehrere Container-Replikate aus).
  • Versucht, UV_THREADPOOL_SIZE zur Geschwindigkeitssteigerung zu erhöhen? Nur wenn Sie I/O-gebunden bei Pool-gestützten Operationen sind (viele gleichzeitige fs/Krypto-Aufrufe). Es wird CPU-gebundenes JavaScript niemals beschleunigen.

Ein zukunftsgerichteter Hinweis für Code, den Sie heute schreiben: Ab Node.js 27 im Oktober 2026 wechselt das Projekt zu einem Major-Release pro Jahr und beendet den geraden/ungeraden Rhythmus. Verfolgen Sie den Node.js-Release-Zeitplan und entwickeln Sie gegen das aktuelle LTS.

Das mentale Modell, das all dies klar hält, ist, aufzuhören zu fragen „Ist Node single-threaded?” und stattdessen zu fragen „Was ist mein Engpass?” Warten ist die Aufgabe des Event Loops; paralleles Berechnen ist die Aufgabe des Worker-Pools; das Verteilen von Last über Kerne ist die Aufgabe von Cluster. Analysieren Sie den langsamen Endpunkt, identifizieren Sie, ob er beim Warten oder beim Berechnen feststeckt, und das richtige Werkzeug ergibt sich direkt daraus — überprüfen Sie dann die Lösung mit monitorEventLoopDelay, bevor Sie sie deployen.

Häufig gestellte Fragen

Was ist der Unterschied zwischen Worker Threads und dem Cluster-Modul in Node.js?

Worker Threads sind separate Threads innerhalb eines Prozesses, jeder ein V8-Isolat mit eigenem Event Loop, die Speicher über einen SharedArrayBuffer teilen können; Cluster forkt separate Prozesse mit vollständig isoliertem Speicher, die über IPC kommunizieren und sich einen lauschenden Socket teilen. Verwenden Sie Worker Threads, um CPU-gebundenes JavaScript vom Request-Thread zu entlasten, und Cluster, um I/O-gebundene Last über CPU-Kerne zu skalieren. Hochdurchsatzfähige Dienste kombinieren oft beides.

Warum macht das Erhöhen von UV_THREADPOOL_SIZE meinen CPU-gebundenen Code nicht schneller?

Der libuv-Thread-Pool führt Operationen auf C-Ebene aus, wie Dateisystemaufrufe, dns.lookup, asynchrone Krypto und zlib — nicht Ihr JavaScript. Das Erhöhen von UV_THREADPOOL_SIZE erhöht nur die Anzahl dieser I/O-artigen Operationen, die gleichzeitig ausgeführt werden; es beschleunigt niemals CPU-gebundenes JavaScript, da dieser Code weiterhin auf dem einzelnen JS-Thread ausgeführt wird. Für CPU-Arbeit benötigen Sie einen Worker Thread, der JavaScript parallel in seinem eigenen V8-Isolat ausführt.

Verarbeitet Node.js Netzwerkanfragen über den libuv-Thread-Pool?

Nein. Netzwerk-Sockets werden durch den Polling-Mechanismus des Betriebssystems verarbeitet — epoll unter Linux, kqueue unter macOS und BSD sowie IOCP unter Windows — und erscheinen direkt in der Poll-Phase des Event Loops. Der libuv-Thread-Pool bedient Dateisystemoperationen, dns.lookup über getaddrinfo, asynchrone Krypto und zlib, aber keine Netzwerk-I/O. Beachten Sie, dass dns.resolve-Funktionen die c-ares-Bibliothek verwenden und den Pool ebenfalls umgehen, sodass 'DNS nutzt den Pool' nur für dns.lookup zutrifft.

Können Worker Threads JavaScript-Objekte mit dem Main-Thread teilen?

Nein. Worker kommunizieren über Message Passing, und Daten, die über workerData oder postMessage übergeben werden, werden tief kopiert unter Verwendung des HTML Structured Clone Algorithms — Funktionen, Klassen-Prototypen und Live-Referenzen überleben den Transfer nicht. Die einzige Möglichkeit, Speicher zu teilen, ist ein SharedArrayBuffer oder das Übertragen eines ArrayBuffer, was das Eigentum verschiebt statt zu kopieren. Diese standardmäßige Isolation vermeidet die Locks und Race Conditions des Shared-Memory-Threading in Sprachen wie C++ oder Java.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.