Nodejs Event Loop


141

Esistono internamente due loop di eventi nell'architettura nodejs?

  • libev / libuv
  • ciclo di eventi javascript v8

Su una richiesta I / O il nodo mette in coda la richiesta a libeio che a sua volta notifica la disponibilità dei dati tramite eventi usando libev e infine quegli eventi sono gestiti dal loop di eventi v8 usando i callback?

Fondamentalmente, come sono integrati libev e libeio nell'architettura nodejs?

Sono disponibili documenti per fornire un quadro chiaro dell'architettura interna di nodejs?

Risposte:


175

Ho letto personalmente il codice sorgente di node.js e v8.

Mi sono imbattuto in un problema simile come te quando ho provato a capire l'architettura node.js per scrivere moduli nativi.

Quello che sto postando qui è la mia comprensione di node.js e questo potrebbe anche essere un po 'fuori strada.

  1. Libev è il loop di eventi che in realtà viene eseguito internamente in node.js per eseguire semplici operazioni di loop di eventi. È stato scritto originariamente per i sistemi * nix. Libev fornisce un loop di eventi semplice ma ottimizzato per l'esecuzione del processo. Puoi leggere di più su libev qui .

  2. LibEio è una libreria per eseguire output di input in modo asincrono. Gestisce descrittori di file, gestori di dati, socket ecc. Puoi leggere di più qui qui .

  3. LibUv è uno strato di astrazione nella parte superiore di libeio, libev, c-ares (per DNS) e iocp (per windows asincrono-io). LibUv esegue, mantiene e gestisce tutto lo io e gli eventi nel pool di eventi. (in caso di libeio threadpool). Dovresti dare un'occhiata al tutorial di Ryan Dahl su libUv. Ciò inizierà a dare più senso a te su come funziona libUv e poi capirai come node.js funziona sulla parte superiore di libuv e v8.

Per capire solo il loop degli eventi javascript dovresti considerare di guardare questi video

Per vedere come viene usato libeio con node.js per creare moduli asincroni, dovresti vedere questo esempio .

Fondamentalmente ciò che accade all'interno di node.js è che il ciclo v8 esegue e gestisce tutte le parti javascript e i moduli C ++ [quando sono in esecuzione in un thread principale (come nella documentazione ufficiale node.js stesso è a thread singolo)]. Quando si trova all'esterno del thread principale, libev e libeio lo gestiscono nel pool di thread e libev fornisce l'interazione con il loop principale. Quindi dalla mia comprensione, node.js ha 1 loop di eventi permanente: quello è il loop di eventi v8. Per gestire le attività asincrone in C ++ sta usando un threadpool [via libeio & libev].

Per esempio:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Ciò che appare in tutti i moduli di solito chiama la funzione Tasknel threadpool. Al termine, chiama la AfterTaskfunzione nel thread principale. Considerando che Eio_REQUESTè il gestore della richiesta che può essere una struttura / oggetto il cui motivo è quello di fornire la comunicazione tra il threadpool e il thread principale.


Affidarsi al fatto che libuv utilizza libev internamente è un buon modo per rendere il codice non multipiattaforma. Dovresti preoccuparti solo dell'interfaccia pubblica di libuv.
Raynos,

1
@Raynos libuv ha lo scopo di assicurarsi che le sue librerie multiple di x-platfousing. Giusto ? quindi usare libuv è una buona idea
ShrekOverflow,

1
@Abhishek da Doc process.nextTick: nel loop successivo attorno al loop degli eventi, chiama questo callback. Questo non è un semplice alias per setTimeout (fn, 0), è molto più efficiente. A quale ciclo di eventi fa riferimento? Ciclo di eventi V8?
Tamil,

2
Nota che libuv non è più implementato su libev .
Strcat,

4
C'è un modo per "vedere" questo evento que? Mi piacerebbe poter vedere l'ordine delle chiamate nello stack e vedere le nuove funzioni che vengono spinte lì per capire meglio cosa sta succedendo ... c'è qualche variabile che ti dice cosa è stato spinto nell'evento que?
tbarbe,

20

Sembra che alcune delle entità discusse (ad es. Libev ecc.) Abbiano perso rilevanza a causa del fatto che è passato del tempo, ma penso che la domanda abbia ancora un grande potenziale.

Vorrei provare a spiegare il funzionamento del modello guidato dagli eventi con l'aiuto di un esempio astratto, in un ambiente UNIX astratto, nel contesto di Node, ad oggi.

Prospettiva del programma:

  • Il motore di script avvia l'esecuzione dello script.
  • Ogni volta che viene rilevata un'operazione associata alla CPU, viene eseguita in linea (macchina reale), nella sua completezza.
  • Ogni volta che viene rilevata un'operazione associata I / O, la richiesta e il relativo gestore di completamento vengono registrati con un "macchinario eventi" (macchina virtuale)
  • Ripetere le operazioni nello stesso modo sopra fino a quando lo script non termina. Operazione associata alla CPU - eseguire quelle in linea, collegate I / O, richiedere alla macchina come sopra.
  • Al termine dell'I / O, gli ascoltatori vengono richiamati.

Il meccanismo di eventi sopra si chiama libuv AKA event loop framework. Node sfrutta questa libreria per implementare il suo modello di programmazione basato sugli eventi.

La prospettiva del nodo:

  • Avere un thread per ospitare il runtime.
  • Prendi lo script utente.
  • Compilalo in nativo [leva v8]
  • Carica il file binario e salta nel punto di ingresso.
  • Il codice compilato esegue le attività associate alla CPU in linea, utilizzando le primitive di programmazione.
  • Molti I / O e il codice relativo al timer hanno wrapping nativi. Ad esempio, I / O di rete.
  • Quindi le chiamate I / O vengono instradate dallo script ai bridge C ++, con l'handle I / O e il gestore di completamento passati come argomenti.
  • Il codice nativo esercita il ciclo libuv. Acquisisce il loop, accoda un evento di basso livello che rappresenta l'I / O e un wrapper di callback nativo nella struttura del loop libuv.
  • Il codice nativo ritorna allo script - al momento non è stato eseguito alcun I / O!
  • Le voci sopra vengono ripetute molte volte, fino a quando non viene eseguito tutto il codice non I / O, e tutto il codice I / O viene registrato in libuv.
  • Infine, quando non c'è più nulla da eseguire nel sistema, il nodo passa il controllo a libuv
  • libuv entra in azione, raccoglie tutti gli eventi registrati, interroga il sistema operativo per ottenere la loro operatività.
  • Quelli che sono pronti per l'I / O in una modalità non bloccante, vengono prelevati, I / O eseguiti e i loro callback emessi. Uno dopo l'altro.
  • Quelli che non sono ancora pronti (ad esempio un socket letto, per il quale l'altro endpoint non ha ancora scritto nulla) continueranno a essere sondati con il sistema operativo fino a quando non saranno disponibili.
  • Il loop mantiene internamente un timer sempre crescente. Quando l'applicazione richiede un callback differito (come setTimeout), questo valore di timer interno viene sfruttato per calcolare il momento giusto per l'attivazione del callback.

Mentre la maggior parte delle funzionalità sono soddisfatte in questo modo, alcune (versioni asincrone) delle operazioni sui file vengono eseguite con l'aiuto di thread aggiuntivi, ben integrati nel libuv. Mentre le operazioni di I / O di rete possono attendere in attesa di un evento esterno come l'altro endpoint che risponde con dati ecc., Le operazioni sui file richiedono un po 'di lavoro dal nodo stesso. Ad esempio, se apri un file e aspetti che la fd sia pronta con i dati, non accadrà, dato che nessuno sta leggendo in realtà! Allo stesso tempo, se leggi dal file inline nel thread principale, può potenzialmente bloccare altre attività nel programma e può causare problemi visibili, poiché le operazioni sui file sono molto lente rispetto alle attività associate alla cpu. Quindi i thread di lavoro interni (configurabili tramite la variabile di ambiente UV_THREADPOOL_SIZE) vengono impiegati per operare su file,

Spero che questo ti aiuti.


Come hai conosciuto queste cose, puoi indicarmi la fonte?
Suraj Jain,

18

Un'introduzione a libuv

Il progetto node.js è iniziato nel 2009 come ambiente JavaScript disaccoppiato dal browser. Usando il V8 di Google e il libev di Marc Lehmann , node.js ha combinato un modello di I / O - eventual - con un linguaggio che ben si adattava allo stile di programmazione; a causa del modo in cui era stato modellato dai browser. Con la crescente popolarità di node.js, era importante farlo funzionare su Windows, ma libev funzionava solo su Unix. L'equivalente di Windows dei meccanismi di notifica degli eventi del kernel come kqueue o (e) poll è IOCP. libuv era un'astrazione attorno a libev o IOCP a seconda della piattaforma, fornendo agli utenti un'API basata su libev. Nella versione node-v0.9.0 di libuv libev è stato rimosso .

Anche un'immagine che descrive il Loop degli eventi in Node.js di @ BusyRich


Aggiornamento 05/09/2017

Per questo ciclo di eventi del documento Node.js ,

Il diagramma seguente mostra una panoramica semplificata dell'ordine delle operazioni del loop di eventi.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

nota: ogni casella verrà definita "fase" del loop degli eventi.

Panoramica delle fasi

  • timer : questa fase esegue i callback programmati da setTimeout()e setInterval().
  • Callback I / O : esegue quasi tutti i callback ad eccezione dei callback ravvicinati , quelli programmati dai timer e setImmediate().
  • inattivo, preparare : usato solo internamente.
  • poll : recupera nuovi eventi I / O; il nodo si bloccherà qui quando appropriato.
  • check : setImmediate()qui vengono richiamati i callback.
  • richiamate ravvicinate : ad es socket.on('close', ...).

Tra ogni esecuzione del ciclo di eventi, Node.js verifica se è in attesa di I / O asincroni o timer e si spegne in modo pulito se non ce ne sono.


Lo hai citato " In the node-v0.9.0 version of libuv libev was removed", ma non c'è una descrizione a riguardo in nodejs changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . E se libev viene rimosso, ora come viene eseguito l'I / O asincrono in nodejs?
intekhab,

@intekhab, per questo link , penso che il libuv basato su libeio possa essere usato come loop di eventi in node.js.
zangw

@intekhab penso che libuv stia implementando tutte le funzionalità relative a I / O e polling. qui controlla in questo documento: docs.libuv.org/en/v1.x/loop.html
mohit kaushik

13

Esiste un loop di eventi nell'architettura NodeJs.

Modello di loop eventi Node.js

Le applicazioni di nodo vengono eseguite in un modello basato su eventi a thread singolo. Tuttavia, Node implementa un pool di thread in background per consentire l'esecuzione del lavoro.

Node.js aggiunge lavoro a una coda di eventi e quindi ha un singolo thread che esegue un ciclo di eventi raccogliendolo. Il ciclo di eventi prende l'elemento in alto nella coda degli eventi, lo esegue e quindi prende l'elemento successivo.

Quando si esegue un codice più longevo o con I / O di blocco, invece di chiamare direttamente la funzione, aggiunge la funzione alla coda degli eventi insieme a un callback che verrà eseguito al termine della funzione. Quando tutti gli eventi sulla coda degli eventi Node.js sono stati eseguiti, l'applicazione Node.js termina.

Il loop degli eventi inizia a riscontrare problemi quando le nostre funzioni dell'applicazione si bloccano sull'I / O.

Node.js utilizza i callback degli eventi per evitare di dover attendere il blocco dell'I / O. Pertanto, tutte le richieste che eseguono I / O di blocco vengono eseguite su un thread diverso in background.

Quando un evento che blocca l'I / O viene recuperato dalla coda degli eventi, Node.js recupera un thread dal pool di thread ed esegue la funzione lì anziché sul thread del ciclo di eventi principale. Ciò impedisce all'I / O di blocco di trattenere il resto degli eventi nella coda degli eventi.


8

C'è solo un loop di eventi fornito da libuv, V8 è solo un motore di runtime JS.


1

Come principiante di JavaScript, ho avuto anche lo stesso dubbio, NodeJS contiene 2 loop di eventi ?. Dopo una lunga ricerca e una discussione con uno dei collaboratori di V8, ho ottenuto i seguenti concetti.

  • Il loop degli eventi è un concetto astratto fondamentale del modello di programmazione JavaScript. Quindi il motore V8 fornisce un'implementazione predefinita per il ciclo degli eventi, che gli embedder (browser, nodo) possono sostituire o estendere . Ragazzi, potete trovare l'implementazione predefinita V8 del loop degli eventi qui
  • In NodeJS esiste un solo ciclo di eventi , fornito dal runtime del nodo. L'implementazione del loop di eventi predefinito V8 è stata sostituita con l'implementazione del loop di eventi NodeJS

0

La pbkdf2funzione ha l'implementazione JavaScript ma in realtà delega tutto il lavoro da eseguire sul lato C ++.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

risorsa: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Il modulo Libuv ha un'altra responsabilità che è rilevante per alcune funzioni molto particolari nella libreria standard.

Per alcune chiamate di funzione di libreria standard, il lato C ++ del nodo e Libuv decidono di eseguire calcoli costosi al di fuori del ciclo degli eventi.

Invece fanno uso di qualcosa chiamato pool di thread, il pool di thread è una serie di quattro thread che possono essere utilizzati per eseguire attività costose dal punto di vista computazionale come la pbkdf2funzione.

Di default Libuv crea 4 thread in questo pool di thread.

Oltre ai thread utilizzati nel loop degli eventi, ci sono altri quattro thread che possono essere utilizzati per scaricare calcoli costosi che devono avvenire all'interno della nostra applicazione.

Molte delle funzioni incluse nella libreria standard Node utilizzano automaticamente questo pool di thread. La pbkdf2funzione è una di queste.

La presenza di questo pool di thread è molto significativa.

Quindi Node non è veramente single thread, perché ci sono altri thread che Node utilizza per svolgere alcune attività computazionalmente costose.

Se il pool di eventi fosse responsabile dell'esecuzione di un'attività computazionalmente costosa, la nostra applicazione Node non potrebbe fare nient'altro.

La nostra CPU esegue tutte le istruzioni all'interno di un thread una per una.

Usando il pool di thread possiamo fare altre cose all'interno di un loop di eventi mentre si verificano calcoli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.