JavaScript er et single-threaded sprog og samtidig også ikke-blokerende, asynkront og concurrent. Denne artikel vil forklare dig, hvordan det sker.
Runtime
JavaScript er et fortolket sprog, ikke et kompileret. Det betyder, at det har brug for en fortolker, som konverterer JS Kode til en maskinkode. Der findes flere typer fortolkere (kendt som engines). De mest populære browser-engines er V8 (Chrome), Quantum (Firefox) og WebKit (Safari). V8 bruges i øvrigt også i en populær ikke-browser runtime, Node.js.
Hver motor indeholder en memory heap, en call stack, en event loop, en callback queue og en WebAPI med HTTP-anmodninger, timere, events osv., alt sammen implementeret på sin egen måde for hurtigere og mere sikker fortolkning af JS-koden.
Grundlæggende JS runtime-arkitektur. Forfatter: Alex Zlatkov
Enkelt tråd
Et single-thread sprog er et sprog med en enkelt call stack og en enkelt memory heap. Det betyder, at det kun kører én ting ad gangen.
A stak er en kontinuerlig hukommelsesregion, der tildeler lokal kontekst til hver udført funktion.
A bunke er en meget større region, der gemmer alt, hvad der er allokeret dynamisk.
A Kald stakken er en datastruktur, som grundlæggende registrerer, hvor vi er i programmet.
Kald på stakken
Lad os skrive en simpel kode og følge med i, hvad der sker i kaldestakken.
Som du kan se, bliver funktionerne føjet til stakken, udført og senere slettet. Det er den såkaldte LIFO-måde - Last In, First Out. Hver post i opkaldsstakken kaldes en Stakramme.
Kendskab til opkaldsstakken er nyttig, når man skal læse fejlstakspor. Generelt står den nøjagtige årsag til fejlen øverst i første linje, selv om rækkefølgen af kodeafviklingen er nedefra og op.
Nogle gange kan du håndtere en populær fejl, der meddeles af Maksimal størrelse på opkaldsstakken overskredet. Det er nemt at få dette ved hjælp af rekursion:
funktion foo() {
foo()
}
foo()
og vores browser eller terminal fryser. Hver browser, selv deres forskellige versioner, har en forskellig grænse for størrelsen på opkaldsstakken. I langt de fleste tilfælde er de tilstrækkelige, og problemet bør søges et andet sted.
Blokeret kaldestak
Her er et eksempel på blokering af JS-tråden. Lad os prøve at læse en Fjols fil og en bar ved hjælp af Knudepunkt.js synkron funktion readFileSync.
Dette er en loopet GIF. Som du kan se, venter JS-motoren, indtil det første kald i readFileSync er afsluttet. Men det vil ikke ske, fordi der ikke er nogen Fjols filen, så den anden funktion bliver aldrig kaldt.
Asynkron adfærd
Men JS kan også være ikke-blokerende og opføre sig, som om den var flertrådet. Det betyder, at den ikke venter på svaret på et API-kald, I/O-begivenheder osv. og kan fortsætte med at udføre koden. Det er muligt takket være JS-motorerne, som (under motorhjelmen) bruger ægte multi-threading-sprog som C++ (Chrome) eller Rust (Firefox). De giver os web-API'en under browserhætten eller f.eks. I/O API under Node.js.
I GIF'en ovenfor kan vi se, at den første funktion skubbes til kaldestakken, og Hej udføres med det samme i konsollen.
Derefter kalder vi setTimeout funktion, der leveres af browserens WebAPI. Den går til opkaldsstakken og dens asynkrone tilbagekald Fjols funktionen går til WebApi-køen, hvor den venter på opkaldet, der er sat til at ske efter 3 sekunder.
I mellemtiden fortsætter programmet koden, og vi ser Hej, jeg er ikke blokeret i konsollen.
Når den er påkaldt, går hver funktion i WebAPI-køen til Kø til tilbagekaldelse. Det er her, funktioner venter, indtil kaldestakken er tom. Når det sker, flyttes de derhen en efter en.
Så når vores setTimeout timeren afslutter nedtællingen, vores Fjols funktionen går til tilbagekaldskøen, venter på, at kaldstakken bliver tilgængelig, går derhen, udføres, og vi ser Hej fra asynkront tilbagekald i konsollen.
Begivenheds-loop
Spørgsmålet er, hvordan runtime ved, at callstacken er tom, og hvordan hændelsen i callback-køen påkaldes? Mød event loop. Det er en del af JS-motoren. Denne proces kontrollerer hele tiden, om opkaldsstakken er tom, og hvis den er, overvåger den, om der er en begivenhed i tilbagekaldskøen, som venter på at blive aktiveret.
Det er al magien bag kulisserne!
Afrunding af teorien
Samtidighed og parallelisme
Samtidighed betyder, at man udfører flere opgaver på samme tid, men ikke samtidigt. F.eks. arbejder to opgaver i overlappende tidsperioder.
Parallelisme betyder at udføre to eller flere opgaver samtidigt, f.eks. at udføre flere beregninger på samme tid.
Tråde og processer
Tråde er en sekvens af kodeudførelse, som kan udføres uafhængigt af hinanden.
Proces er en instans af et kørende program. Et program kan have flere processer.
Synkron og asynkron
I synkron I programmering udføres opgaverne en efter en. Hver opgave venter på, at en tidligere opgave er afsluttet, og udføres først derefter.
I asynkron programmering, når en opgave er udført, kan du skifte til en anden opgave uden at vente på, at den forrige er færdig.
Synkron og asynkron i et enkelt- og flertrådet miljø
Synkron med en enkelt tråd: Opgaverne udføres en efter en. Hver opgave venter på, at den foregående opgave bliver udført.
Synkron med flere tråde: Opgaverne udføres i forskellige tråde, men venter på andre opgaver, der udføres i en anden tråd.
Asynkron med en enkelt tråd: Opgaver begynder at blive udført uden at vente på, at en anden opgave bliver færdig. På et givet tidspunkt kan der kun udføres en enkelt opgave.
Asynkron med flere tråde: Opgaverne udføres i forskellige tråde uden at vente på, at andre opgaver bliver færdige, og de afslutter deres udførelse uafhængigt af hinanden.
JavaScript-klassificering
Hvis vi ser på, hvordan JS-motorer fungerer under motorhjelmen, kan vi klassificere JS som et asynkront og enkelttrådet fortolket sprog. Ordet "fortolket" er meget vigtigt, fordi det betyder, at sproget altid vil være runtime-afhængigt og aldrig så hurtigt som kompilerede sprog med indbygget multi-threading.
Det er bemærkelsesværdigt, at Node.js kan opnå ægte multi-threading, forudsat at hver tråd startes som en separat proces. Der findes biblioteker til dette, men Node.js har en indbygget funktion, der hedder Arbejdstråde.
Alle event-loop-GIF'er kommer fra Lupe applikation skabt af Philip Roberts, hvor du kan teste dine asynkrone scenarier.