Il nodo ha un paradigma completamente diverso e una volta catturato correttamente, è più facile vedere questo modo diverso di risolvere i problemi. Non hai mai bisogno di più thread in un'applicazione Node (1) perché hai un modo diverso di fare la stessa cosa. Crei più processi; ma è molto molto diverso da, ad esempio, come fa l'mpm Prefork di Apache Web Server.
Per ora, pensiamo di avere un solo core della CPU e svilupperemo un'applicazione (alla maniera di Node) per fare un po 'di lavoro. Il nostro compito è elaborare un file di grandi dimensioni che scorre sul suo contenuto byte per byte. Il modo migliore per il nostro software è iniziare il lavoro dall'inizio del file, seguirlo byte per byte fino alla fine.
- Ehi, Hasan, suppongo che tu sia un principiante o una scuola molto vecchia dai tempi di mio nonno !!! Perché non crei alcuni thread e lo rendi molto più veloce?
- Oh, abbiamo solo un core della CPU.
-- E allora? Crea dei thread amico, rendilo più veloce!
- Non funziona così. Se creo dei thread, lo renderò più lento. Perché aggiungerò un sacco di overhead al sistema per passare da un thread all'altro, cercando di dare loro una giusta quantità di tempo e, all'interno del mio processo, tentando di comunicare tra questi thread. Oltre a tutti questi fatti, dovrò anche pensare a come dividere un singolo lavoro in più pezzi che possono essere eseguiti in parallelo.
- Va bene, va bene, vedo che sei povero. Usiamo il mio computer, ha 32 core!
- Wow, sei fantastico mio caro amico, grazie mille. Lo apprezzo!
Poi torniamo al lavoro. Ora abbiamo 32 core cpu grazie al nostro ricco amico. Le regole che dobbiamo rispettare sono appena cambiate. Ora vogliamo utilizzare tutta questa ricchezza che ci viene data.
Per utilizzare più core, dobbiamo trovare un modo per dividere il nostro lavoro in parti che possiamo gestire in parallelo. Se non fosse Node, useremmo i thread per questo; 32 thread, uno per ogni core della cpu. Tuttavia, poiché abbiamo Node, creeremo 32 processi Node.
I thread possono essere una buona alternativa ai processi Node, forse anche un modo migliore; ma solo in un tipo specifico di lavoro in cui il lavoro è già definito e abbiamo il controllo completo su come gestirlo. Oltre a questo, per ogni altro tipo di problema in cui il lavoro viene dall'esterno in un modo su cui non abbiamo il controllo e vogliamo rispondere il più rapidamente possibile, il modo di Node è indiscutibilmente superiore.
- Ehi, Hasan, lavori ancora a thread singolo? Cosa c'è di sbagliato in te, amico? Ti ho appena fornito quello che volevi. Non hai più scuse. Crea thread, fallo funzionare più velocemente.
- Ho diviso il lavoro in pezzi e ogni processo lavorerà su uno di questi pezzi in parallelo.
- Perché non crei thread?
- Scusa, non credo sia utilizzabile. Puoi prendere il tuo computer se vuoi?
- No okay, sto bene, solo che non capisco perché non usi i thread?
- Grazie per il computer. :) Ho già diviso il lavoro in pezzi e creo processi per lavorare su questi pezzi in parallelo. Tutti i core della CPU saranno completamente utilizzati. Potrei farlo con i thread invece che con i processi; ma Node ha questo modo e il mio capo Parth Thakkar vuole che usi Node.
- Va bene, fammi sapere se ti serve un altro computer. : p
Se creo 33 processi, invece di 32, lo scheduler del sistema operativo metterà in pausa un thread, avvierà l'altro, lo metterà in pausa dopo alcuni cicli, riavvierà l'altro ... Questo è un sovraccarico non necessario. Io non lo voglio. In effetti, su un sistema con 32 core, non vorrei nemmeno creare esattamente 32 processi, 31 può essere più bello . Perché non è solo la mia applicazione che funzionerà su questo sistema. Lasciare un po 'di spazio per altre cose può andare bene, soprattutto se abbiamo 32 stanze.
Credo che ora siamo sulla stessa pagina sull'utilizzo completo dei processori per attività ad alta intensità di CPU .
- Hmm, Hasan, mi dispiace di averti preso in giro un po '. Credo di capirti meglio adesso. Ma c'è ancora qualcosa per cui ho bisogno di una spiegazione: qual è tutto il brusio dell'esecuzione di centinaia di thread? Ho letto ovunque che i thread sono molto più veloci da creare e stupidi rispetto ai processi di fork? Esegui il fork dei processi invece dei thread e pensi che sia il massimo che potresti ottenere con Node. Quindi Node non è appropriato per questo tipo di lavoro?
- Nessun problema, anch'io sono a posto. Tutti dicono queste cose quindi penso di essere abituato a sentirle.
-- Così? Node non va bene per questo?
- Node è perfettamente adatto a questo, anche se anche i thread possono essere buoni. Per quanto riguarda l'overhead di creazione di thread / processi; su cose che ripeti molto, ogni millisecondo conta. Tuttavia, creo solo 32 processi e ci vorrà un po 'di tempo. Accadrà solo una volta. Non farà alcuna differenza.
- Quando voglio creare migliaia di thread, allora?
- Non vuoi mai creare migliaia di thread. Tuttavia, su un sistema che sta eseguendo un lavoro che proviene dall'esterno, come un server web che elabora le richieste HTTP; se stai usando un thread per ogni richiesta, creerai molti thread, molti dei quali.
- Node è diverso, però? Destra?
-- Si, esattamente. È qui che Node brilla davvero. Come un thread è molto più leggero di un processo, una chiamata di funzione è molto più leggero di un thread. Il nodo chiama le funzioni, invece di creare thread. Nell'esempio di un server web, ogni richiesta in arrivo provoca una chiamata di funzione.
- Hmm, interessante; ma puoi eseguire solo una funzione alla volta se non stai usando più thread. Come può funzionare quando molte richieste arrivano al server web contemporaneamente?
- Hai perfettamente ragione su come vengono eseguite le funzioni, una alla volta, mai due in parallelo. Voglio dire in un singolo processo, viene eseguito un solo ambito di codice alla volta. L'utilità di pianificazione del sistema operativo non viene a mettere in pausa questa funzione e passa a un'altra, a meno che non interrompa il processo per dare tempo a un altro processo, non a un altro thread nel nostro processo. (2)
- Allora come può un processo gestire 2 richieste alla volta?
- Un processo può gestire decine di migliaia di richieste alla volta purché il nostro sistema disponga di risorse sufficienti (RAM, rete, ecc.). Il modo in cui vengono eseguite queste funzioni è LA DIFFERENZA CHIAVE.
- Hmm, dovrei essere eccitato adesso?
- Forse :) Node esegue un ciclo su una coda. In questa coda ci sono i nostri lavori, cioè le chiamate che abbiamo avviato per elaborare le richieste in arrivo. Il punto più importante qui è il modo in cui progettiamo le nostre funzioni per l'esecuzione. Invece di iniziare a elaborare una richiesta e fare in modo che il chiamante attenda fino al termine del lavoro, terminiamo rapidamente la nostra funzione dopo aver svolto una quantità accettabile di lavoro. Quando arriviamo a un punto in cui dobbiamo aspettare che un altro componente faccia del lavoro e ci restituisca un valore, invece di aspettare quello, finiamo semplicemente la nostra funzione aggiungendo il resto del lavoro alla coda.
- Sembra troppo complesso?
- No no, potrei sembrare complesso; ma il sistema stesso è molto semplice e ha perfettamente senso.
Ora voglio smetterla di citare il dialogo tra questi due sviluppatori e concludere la mia risposta dopo un ultimo rapido esempio di come funzionano queste funzioni.
In questo modo, stiamo facendo ciò che normalmente farebbe OS Scheduler. Mettiamo in pausa il nostro lavoro a un certo punto e lasciamo che altre chiamate di funzione (come altri thread in un ambiente multi-thread) vengano eseguite fino a quando non torniamo di nuovo a turno. Questo è molto meglio che lasciare il lavoro a OS Scheduler che cerca di dare solo il tempo a ogni thread sul sistema. Sappiamo cosa stiamo facendo molto meglio di OS Scheduler e ci si aspetta che ci fermiamo quando dovremmo fermarci.
Di seguito è riportato un semplice esempio in cui apriamo un file e lo leggiamo per lavorare sui dati.
Modo sincrono:
Open File
Repeat This:
Read Some
Do the work
Modo asincrono:
Open File and Do this when it is ready: // Our function returns
Repeat this:
Read Some and when it is ready: // Returns again
Do some work
Come vedi, la nostra funzione chiede al sistema di aprire un file e non attende che venga aperto. Termina da solo fornendo i passaggi successivi dopo che il file è pronto. Quando torniamo, Node esegue altre chiamate di funzione sulla coda. Dopo aver eseguito tutte le funzioni, il ciclo di eventi passa al turno successivo ...
In sintesi, Node ha un paradigma completamente diverso dallo sviluppo multi-threaded; ma questo non vuol dire che manchi di cose. Per un lavoro sincrono (dove possiamo decidere l'ordine e il modo di elaborazione), funziona così come il parallelismo multi-thread. Per un lavoro che proviene dall'esterno come richieste a un server, è semplicemente superiore.
(1) A meno che tu non stia creando librerie in altri linguaggi come C / C ++, nel qual caso non crei ancora thread per dividere i lavori. Per questo tipo di lavoro hai due thread uno dei quali continuerà la comunicazione con Node mentre l'altro fa il lavoro vero e proprio.
(2) In effetti, ogni processo Node ha più thread per gli stessi motivi che ho menzionato nella prima nota a piè di pagina. Tuttavia, non è possibile che 1000 thread eseguano lavori simili. Questi thread aggiuntivi servono per cose come accettare eventi di I / O e gestire la messaggistica tra processi.
AGGIORNAMENTO (in risposta a una buona domanda nei commenti)
@ Marco, grazie per le critiche costruttive. Nel paradigma di Node, non dovresti mai avere funzioni che impiegano troppo tempo per essere elaborate a meno che tutte le altre chiamate nella coda non siano progettate per essere eseguite una dopo l'altra. In caso di compiti computazionalmente costosi, se guardiamo il quadro completo, vediamo che non è una questione di "Dobbiamo usare thread o processi?" ma una domanda di "Come possiamo dividere queste attività in modo ben bilanciato in sotto-attività che possiamo eseguirle in parallelo utilizzando più core della CPU sul sistema?" Supponiamo che elaboreremo 400 file video su un sistema con 8 core. Se vogliamo elaborare un file alla volta, allora abbiamo bisogno di un sistema che elabori parti diverse dello stesso file, nel qual caso, forse, un sistema a processo singolo multi-thread sarà più facile da costruire e ancora più efficiente. Possiamo ancora usare Node per questo eseguendo più processi e passando messaggi tra di loro quando è necessaria la condivisione / comunicazione dello stato. Come ho detto prima, un approccio multi-processo con Node ècosì come un approccio multi-thread in questo tipo di attività; ma non di più. Ancora una volta, come ho detto prima, la situazione in cui Node brilla è quando abbiamo queste attività in ingresso al sistema da più fonti poiché mantenere molte connessioni contemporaneamente è molto più leggero in Node rispetto a un thread per connessione o processo per connessione sistema.
Per quanto riguarda le setTimeout(...,0)
chiamate; a volte può essere necessaria una pausa durante un'attività che richiede tempo per consentire alle chiamate in coda di avere la loro parte di elaborazione. Dividere le attività in modi diversi può salvarti da queste; ma ancora, questo non è davvero un trucco, è solo il modo in cui funzionano le code degli eventi. Inoltre, usare process.nextTick
per questo scopo è molto meglio poiché quando lo usi setTimeout
, sarà necessario calcolare e controllare il tempo trascorso mentre process.nextTick
è semplicemente quello che vogliamo veramente: "Ehi compito, torna alla fine della coda, hai usato la tua quota! "