JavaScript är ett enkeltrådat språk och samtidigt också icke-blockerande, asynkront och samtidigt. Den här artikeln kommer att förklara för dig hur det händer.
Runtid
JavaScript är ett tolkat språk, inte ett kompilerat. Det innebär att det behöver en tolk som konverterar JS kod till en maskinkod. Det finns flera olika typer av tolkar (s.k. motorer). De mest populära webbläsarmotorerna är V8 (Chrome), Quantum (Firefox) och WebKit (Safari). V8 används för övrigt också i en populär runtime som inte är för webbläsare, Node.js.
Varje motor innehåller en minneshög, en anropsstack, en händelseslinga, en återuppringningskö och ett WebAPI med HTTP-förfrågningar, timers, händelser etc., allt implementerat på sitt eget sätt för snabbare och säkrare tolkning av JS-koden.
Grundläggande JS runtime-arkitektur. Författare: Alex Zlatkov
Enstaka tråd
Ett språk med en enda tråd är ett språk med en enda anropsstack och en enda minneshög. Det betyder att det bara kör en sak åt gången.
A stack är en kontinuerlig minnesregion som allokerar en lokal kontext för varje exekverad funktion.
A hög är en mycket större region som lagrar allt som allokeras dynamiskt.
A anropsstapel är en datastruktur som i princip registrerar var vi befinner oss i programmet.
Samtalsstack
Låt oss skriva en enkel kod och följa vad som händer i anropsstacken.
Som du kan se läggs funktionerna till i stacken, utförs och tas sedan bort. Det är det så kallade LIFO-sättet - Last In, First Out. Varje post i anropsstacken kallas en stapelram.
Kunskap om anropsstacken är användbar för att läsa felstackspår. I allmänhet står den exakta orsaken till felet högst upp på första raden, även om ordningen på kodkörningen är nedifrån och upp.
Ibland kan du hantera ett populärt fel, meddelat av Maximal storlek på anropsstacken överskriden. Det är lätt att få detta med hjälp av rekursion:
funktion foo() {
foo()
}
foo()
och vår webbläsare eller terminal fryser. Varje webbläsare, även deras olika versioner, har en annan gräns för anropsstackens storlek. I de allra flesta fall är de tillräckliga och problemet bör sökas någon annanstans.
Stack för blockerade samtal
Här är ett exempel på blockering av JS-tråden. Låt oss försöka läsa en Foo fil och en bar med hjälp av Nod.js synkron funktion readFileSync.
Detta är en loopad GIF. Som du ser väntar JS-motorn tills det första anropet i readFileSync är slutförd. Men detta kommer inte att ske eftersom det inte finns någon Foo filen, så den andra funktionen kommer aldrig att anropas.
Asynkront beteende
JS kan dock också vara icke-blockerande och bete sig som om den vore flertrådig. Det innebär att den inte väntar på svaret från ett API-anrop, I/O-händelser etc. utan kan fortsätta att exekvera koden. Det är möjligt tack vare JS-motorerna som (under huven) använder riktiga flertrådade språk, som C++ (Chrome) eller Rust (Firefox). De förser oss med webb-API under webbläsarens huva eller t.ex. I/O API under Node.js.
I GIF:en ovan ser vi att den första funktionen flyttas till anropsstacken och Hej körs omedelbart i konsolen.
Sedan kallar vi setTimeout funktion som tillhandahålls av webbläsarens WebAPI. Den går till anropsstacken och dess asynkrona återuppringning Foo funktionen går till WebApi-kön, där den väntar på anropet, som är inställt på att ske efter 3 sekunder.
Under tiden fortsätter programmet koden och vi ser Hej. Jag är inte blockerad i konsolen.
Efter att den har anropats går varje funktion i WebAPI-kön till Kö för återuppringning. Det är där funktioner väntar tills anropsstacken är tom. När det sker flyttas de dit en efter en.
Så när vår setTimeout timern avslutar nedräkningen, kommer vår Foo funktionen går till återuppringningskön, väntar tills anropsstacken blir tillgänglig, går dit, exekveras och vi ser Hej från asynkron callback i konsolen.
Evenemangsslinga
Frågan är hur runtime vet att anropsstacken är tom och hur händelsen i callback-kön anropas? Möt händelseslingan. Det är en del av JS-motorn. Den här processen kontrollerar ständigt om anropsstacken är tom och, om den är det, övervakar om det finns en händelse i återuppringningskön som väntar på att bli anropad.
Det är all magi bakom kulisserna!
Sammanfattning av teorin
Samtidighet och parallellism
Samtidighet innebär att man utför flera uppgifter samtidigt men inte samtidigt. T.ex. två arbetsuppgifter som utförs under överlappande tidsperioder.
Parallellism innebär att utföra två eller flera uppgifter samtidigt, t.ex. att utföra flera beräkningar samtidigt.
Trådar och processer
Trådar är en sekvens av kodexekveringar som kan utföras oberoende av varandra.
Process är en instans av ett program som körs. Ett program kan ha flera processer.
Synkron och asynkron
I synkron Vid programmering utförs uppgifterna efter varandra. Varje uppgift väntar på att en tidigare uppgift ska bli klar och utförs först då.
I asynkron programmering, när en uppgift är utförd kan du växla till en annan uppgift utan att vänta på att den föregående ska bli klar.
Synkron och asynkron i en enkel- och flertrådad miljö
Synkron med en enda tråd: Uppgifterna utförs en efter en. Varje uppgift väntar på att den föregående uppgiften ska bli utförd.
Synkron med flera trådar: Uppgifter utförs i olika trådar men väntar på andra uppgifter som utförs i någon annan tråd.
Asynkron med en enda tråd: Uppgifter börjar utföras utan att vänta på att en annan uppgift ska bli klar. Vid en given tidpunkt kan endast en enda uppgift utföras.
Asynkron med flera trådar: Uppgifter utförs i olika trådar utan att vänta på att andra uppgifter ska slutföras och avslutar sina körningar oberoende av varandra.
JavaScript klassificering
Om vi tittar på hur JS-motorerna fungerar under huven kan vi klassificera JS som ett asynkront och enkeltrådat tolkat språk. Ordet "tolkat" är mycket viktigt eftersom det innebär att språket alltid kommer att vara runtime-beroende och aldrig lika snabbt som kompilerade språk med inbyggd multi-threading.
Det är värt att notera att Node.js kan åstadkomma verklig multi-threading, förutsatt att varje tråd startas som en separat process. Det finns bibliotek för detta, men Node.js har en inbyggd funktion som heter Arbetartrådar.
Alla GIF-bilder från evenemangsslingan kommer från Loupe applikation skapad av Philip Roberts, där du kan testa dina asynkrona scenarier.