"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.

Ma cos'è una filettatura e in cosa si differenzia da un processo? Ci sono diverse differenze, ma la più importante per noi è il modo in cui viene allocata la memoria. Si può pensare a un processo come a un'applicazione. All'interno di ogni processo, c'è una porzione di memoria dedicata solo a questo processo. Quindi, un processo non ha accesso alla memoria del secondo e questa proprietà garantisce un'elevata sicurezza. Per stabilire una comunicazione tra i due processi, dobbiamo fare un po' di lavoro. I thread sono diversi. I thread vengono eseguiti all'interno di un processo e condividono la stessa memoria, quindi non c'è alcun problema con la condivisione dei thread. dati.
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 (JS parte), 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