javascript - Cum face Nodejs I/O în culise?


Știu că Node.js folosește pool-ul de thread-uri furnizat de libuv pentru a realiza sarcini concurente. Am citit că Node.js are nevoie doar de unul dintre aceste thread-uri pentru a face toate activitățile dependente de I/O

Atunci cum se întâmplă operațiunile de fișiere non-blocante?

Ce se va întâmpla de fapt în spate între nod, disc și sistemul de operare, dacă parcurg un folder care conține 100 de fișiere și le deschid pe toate folosind readFile()?

Dacă efectuarea aceleași operațiuni într-un alt limbaj multithread și blocant va determina thread-urile să aștepte ca I/O să se termine, atunci cine așteaptă ca I/O să se termine în Node.js?, mai ales când au loc mai multe operațiuni de fișiere simultan?

Mulțumesc!

Răspuns #1

IO de fișiere în Node.js folosește un grup de fire de execuție. Implicit, există 4 fire în grup.

Când un anumit cod Javascript apelează o operațiune pe fișiere, cum ar fi fs.open() în nodejs, apelul este direcționat către un cod nativ din cadrul bibliotecii libuv din implementarea nodejs. Acest cod adaugă operațiunea specifică pe fișiere într-o coadă de elemente care așteaptă un fir de execuție al sistemului de operare. Dacă un fir de execuție al sistemului de operare este disponibil imediat, atunci acel fir primește sarcina de a executa operațiunea pe fișiere, după care funcția originală libuv se întoarce, revenind la codul Javascript inițial, iar nodejs continuă să ruleze alt cod Javascript.

Apoi, la un moment dat mai târziu, firul finalizează operațiunea asupra fișierului și obține un rezultat. Introduce un eveniment în coada de evenimente Node.js cu rezultatul finalizat și callback-ul original transmis cu operațiunea inițială asupra fișierului.

Când Node.js a terminat de executat orice alt Javascript pe care îl rula, ia următorul eveniment din coada de evenimente și apelează callback-ul Javascript asociat cu acel eveniment și îi transmite datele asociate evenimentului. Acest lucru declanșează apelarea callback-ului Javascript care a fost inițial transmis către fs.open().

Dacă, în al doilea pas de mai sus, nu ar fi fost fire disponibile în pool-ul de fire, atunci sarcina este adăugată într-o coadă și libuv returnează imediat controlul înapoi la nodejs pentru a putea rula alt Javascript. Către un moment ulterior, atunci când unul dintre firele existente din pool își termină munca de adăugare a unui eveniment în coada de evenimente, acesta va extrage următorul element din coada pool-ului de fire, îi va oferi firul acum disponibil și îl va lăsa să înceapă să-și execute operațiunea.

În acest fel, toate operațiunile cu fișiere par a fi non-blocante și asincrone din partea Javascript. Ele fie sunt începute imediat dacă există un fir disponibil în pool-ul de fire, fie stau în coada pool-ului de fire până când un fir devine disponibil. Acea parte este invizibilă pentru partea Javascript. În orice caz, operațiunea este inițiată sau pusă în coadă și controlul revine imediat la nodejs pentru a continua rularea altor coduri Javascript. Și, în ambele cazuri, operațiunea se finalizează la un moment dat și callback-ul Javascript asociat operațiunii cu fișierul este apelat prin coada de evenimente Javascript. Aceasta oferă mecanismul asincron, non-blocant și bazat pe evenimente pe care nodejs îl folosește pentru toate operațiunile I/O cu fișiere.