"Non bloccare il ciclo degli eventi..." - probabilmente avrete sentito questa frase molte volte... Non mi sorprende, perché è uno dei presupposti più importanti quando si lavora con Node. Ma c'è anche una seconda "cosa" che si dovrebbe evitare di bloccare: il pool di lavoratori. Se trascurato, può avere un impatto significativo sulle prestazioni dell'applicazione e persino sulla sua sicurezza.
Fili
La cosa principale da ricordare è che ci sono due tipi di thread in Node.js: Thread principale - che viene gestito da Ciclo di eventi, e Pool di lavoratori (pool di thread) - che è il pool di thread
grazie a libuv. Ognuno di essi ha un compito diverso. L'obiettivo del primo è gestire le operazioni di I/O non bloccanti, mentre il secondo è responsabile del lavoro intensivo della CPU e anche dell'I/O bloccante.

But what is a thread and how it’s different than a process? There are several differences, but the most important one for us is how the memory is allocated to them. You can think about a process like about an application. Inside each process, there is a chunk of memory dedicated just for this process. So, one process doesn`t have access to the memory of the second one, and this property ensures high security. To establish communication between them, we must do some work. Threads are different. Threads runs inside a process and share the same memory so there is no problem at all with threads sharing data.
Tuttavia, una questione causa un problema. Si tratta della cosiddetta condizione di gara. I thread possono essere eseguiti contemporaneamente, quindi come facciamo a sapere quale termina per primo? Può accadere che la prima volta che si esegue, la prima operazione termini per prima, mentre la volta successiva può accadere il contrario e che la seconda operazione termini prima della prima. Immaginate di lavorare con operazioni di scrittura/lettura in queste condizioni! Un incubo! A volte è molto difficile scrivere operazioni corrette codice in un ambiente multi-thread.
Inoltre, i linguaggi multi-thread hanno un grande overhead di memoria perché creano un thread separato per ogni richiesta; quindi, se si vogliono chiamare 1000 richieste, creano 1000 thread.
Come affrontare un simile problema? Utilizzando invece un singolo thread! E questo è ciò che Nodo vi offre.

Come JavaScript sviluppatore Vi invito a guardare il film
in cui Bart Belder spiega chiaramente il concetto di ciclo di eventi. Il diagramma qui sopra è tratto dalla sua presentazione. E se non conoscete affatto questi termini, sia Nodo e Libuv hanno una documentazione eccellente 🙂
Informazioni sul blocco
In Sviluppo JavaScript dicono che perché Nodo è a thread singolo e non bloccante, si può ottenere una maggiore concurrency con le stesse risorse rispetto alle soluzioni multi-thread. È vero, ma non è così bello e facile come può sembrare.
Da quando Node.js è a thread singolo (parte JS), i task ad alta intensità di CPU bloccheranno tutte le richieste in corso fino al completamento di quel particolare task. Quindi, è vero che in Node.js è possibile bloccare ogni richiesta solo perché una di esse contiene un'istruzione bloccante. Il codice bloccante significa che richiede più di qualche millisecondo per essere completato. Ma non bisogna confondere i tempi di risposta lunghi con il blocco. La risposta del database può richiedere molto tempo, ma non blocca il processo (l'applicazione).
I metodi bloccanti vengono eseguiti in modo sincrono e quelli non bloccanti in modo asincrono.
Come si può rallentare (o bloccare) il ciclo degli eventi?
- regex vulnerabili - un'espressione regolare vulnerabile è quella per la quale il motore di espressione regolare potrebbe impiegare un tempo esponenziale; è possibile leggere maggiori informazioni al riguardo qui,
- Operazioni JSON su oggetti di grandi dimensioni,
- utilizzando le API sincrone di Nodo invece delle versioni asincrone; tutti i metodi di I/O della libreria standard Node.js forniscono anche le loro versioni asincrone,
- altri errori di programmazione, come i loop infiniti sincroni.
In questo caso, dato che il Worker Pool utilizza un pool di thread, è possibile bloccare anche questi? Sfortunatamente, sì 🙁 Nodo si basa su una filosofia un filo per molti clienti. Supponiamo che un determinato compito eseguito da uno specifico Worker sia molto complesso e richieda più tempo per essere completato. Di conseguenza, il Worker viene bloccato e non può essere utilizzato per eseguire altre attività in sospeso finché le sue istruzioni non vengono eseguite. Come si sarà capito, questo può influire sulle prestazioni. È possibile evitare questi problemi riducendo al minimo la variazione dei tempi dei task utilizzando il partizionamento dei task.
Conclusione
Evitare il blocco, questo è certo. Se proprio potete, scegliete sempre le versioni asincrone delle API di libreria standard. In caso contrario, dopo l'esecuzione dell'applicazione, il client può riscontrare diversi problemi, a partire dalla riduzione del throughput fino al ritiro completo, che è fatale dal punto di vista dell'utente.
Per saperne di più:
Perché si dovrebbe (probabilmente) usare Typescript
Come non uccidere un progetto con cattive pratiche di codifica?
Strategie di recupero dei dati in NextJS