Η JavaScript είναι μια γλώσσα με ένα νήμα και, ταυτόχρονα, μη μπλοκαρισμένη, ασύγχρονη και ταυτόχρονη. Αυτό το άρθρο θα σας εξηγήσει πώς συμβαίνει αυτό.
Χρόνος εκτέλεσης
JavaScript είναι μια διερμηνευμένη γλώσσα, όχι μια μεταγλωττισμένη. Αυτό σημαίνει ότι χρειάζεται έναν διερμηνέα ο οποίος μετατρέπει την JS κωδικός σε κώδικα μηχανής. Υπάρχουν διάφοροι τύποι διερμηνέων (γνωστοί ως μηχανές). Οι πιο δημοφιλείς μηχανές προγραμμάτων περιήγησης είναι οι V8 (Chrome), Quantum (Firefox) και WebKit (Safari). Παρεμπιπτόντως, ο V8 χρησιμοποιείται επίσης σε ένα δημοφιλές πρόγραμμα εκτέλεσης που δεν είναι πρόγραμμα περιήγησης, Node.js.
Κάθε μηχανή περιέχει έναν σωρό μνήμης, μια στοίβα κλήσεων, έναν βρόχο συμβάντων, μια ουρά ανακλήσεων και ένα WebAPI με αιτήσεις HTTP, χρονομετρητές, συμβάντα κ.λπ., όλα υλοποιημένα με τον δικό τους τρόπο για ταχύτερη και ασφαλέστερη ερμηνεία του κώδικα JS.
Βασική αρχιτεκτονική χρόνου εκτέλεσης JS. Συγγραφέας: Δρ: Alex Zlatkov
Μονό νήμα
Μια γλώσσα ενός νήματος είναι μια γλώσσα με μία μόνο στοίβα κλήσεων και έναν μόνο σωρό μνήμης. Αυτό σημαίνει ότι εκτελεί μόνο ένα πράγμα κάθε φορά.
A στοίβα είναι μια συνεχής περιοχή μνήμης, η οποία κατανέμει τοπικό πλαίσιο για κάθε εκτελούμενη συνάρτηση.
A σωρός είναι μια πολύ μεγαλύτερη περιοχή, στην οποία αποθηκεύονται όλα όσα κατανέμονται δυναμικά.
A στοίβα κλήσεων είναι μια δομή δεδομένων που ουσιαστικά καταγράφει πού βρισκόμαστε στο πρόγραμμα.
Στοίβα κλήσεων
Ας γράψουμε έναν απλό κώδικα και ας παρακολουθήσουμε τι συμβαίνει στη στοίβα κλήσεων.
Όπως μπορείτε να δείτε, οι συναρτήσεις προστίθενται στη στοίβα, εκτελούνται και αργότερα διαγράφονται. Είναι ο λεγόμενος τρόπος LIFO - Last In, First Out. Κάθε καταχώρηση στη στοίβα κλήσεων ονομάζεται πλαίσιο στοίβας.
Η γνώση της στοίβας κλήσεων είναι χρήσιμη για την ανάγνωση των ιχνών στοίβας σφαλμάτων. Γενικά, η ακριβής αιτία του σφάλματος βρίσκεται στην κορυφή της πρώτης γραμμής, αν και η σειρά εκτέλεσης του κώδικα είναι από κάτω προς τα πάνω.
Μερικές φορές μπορείτε να αντιμετωπίσετε ένα δημοφιλές σφάλμα, που κοινοποιείται με Υπέρβαση του μέγιστου μεγέθους στοίβας κλήσεων. Είναι εύκολο να το πετύχετε αυτό χρησιμοποιώντας αναδρομή:
function foo() {
foo()
}
foo()
και το πρόγραμμα περιήγησης ή το τερματικό μας παγώνει. Κάθε πρόγραμμα περιήγησης, ακόμη και οι διαφορετικές εκδόσεις τους, έχουν διαφορετικό όριο μεγέθους στοίβας κλήσεων. Στη συντριπτική πλειονότητα των περιπτώσεων, είναι επαρκή και το πρόβλημα θα πρέπει να αναζητηθεί αλλού.
Αποκλεισμένη στοίβα κλήσεων
Ακολουθεί ένα παράδειγμα μπλοκαρίσματος του νήματος JS. Ας προσπαθήσουμε να διαβάσουμε ένα foo αρχείο και ένα μπαρ χρησιμοποιώντας το Κόμβος.js σύγχρονη λειτουργία readFileSync.
Αυτό είναι ένα looped GIF. Όπως βλέπετε, η μηχανή JS περιμένει μέχρι την πρώτη κλήση στο readFileSync έχει ολοκληρωθεί. Αλλά αυτό δεν θα συμβεί επειδή δεν υπάρχει foo αρχείο, οπότε η δεύτερη συνάρτηση δεν θα κληθεί ποτέ.
Ασύγχρονη συμπεριφορά
Ωστόσο, το JS μπορεί επίσης να είναι μη μπλοκαρισμένο και να συμπεριφέρεται σαν να ήταν πολυνηματικό. Αυτό σημαίνει ότι δεν περιμένει την απάντηση μιας κλήσης API, συμβάντα εισόδου/εξόδου κ.λπ. και μπορεί να συνεχίσει την εκτέλεση του κώδικα. Αυτό είναι εφικτό χάρη στις μηχανές JS που χρησιμοποιούν (κάτω από το καπό) πραγματικές γλώσσες πολλαπλών νημάτων, όπως η C++ (Chrome) ή η Rust (Firefox). Μας παρέχουν το Web API κάτω από την κουκούλα του προγράμματος περιήγησης ή ex. I/O API κάτω από το Node.js.
Στην παραπάνω GIF, βλέπουμε ότι η πρώτη συνάρτηση ωθείται στη στοίβα κλήσεων και Γεια σας εκτελείται αμέσως στην κονσόλα.
Στη συνέχεια, καλούμε το setTimeout που παρέχεται από το WebAPI του προγράμματος περιήγησης. Πηγαίνει στη στοίβα κλήσεων και στην ασύγχρονη επανάκλησή της foo η συνάρτηση πηγαίνει στην ουρά του WebApi, όπου περιμένει την κλήση, η οποία έχει οριστεί να γίνει μετά από 3 δευτερόλεπτα.
Εν τω μεταξύ, το πρόγραμμα συνεχίζει τον κώδικα και βλέπουμε Γεια σας. Δεν είμαι μπλοκαρισμένος στην κονσόλα.
Μετά την κλήση της, κάθε συνάρτηση στην ουρά WebAPI πηγαίνει στο αρχείο Ουρά επανάκλησης. Είναι το σημείο όπου οι συναρτήσεις περιμένουν μέχρι να αδειάσει η στοίβα κλήσεων. Όταν συμβεί αυτό, μετακινούνται εκεί μία προς μία.
Έτσι, όταν το setTimeout το χρονόμετρο ολοκληρώνει την αντίστροφη μέτρηση, το foo η συνάρτηση πηγαίνει στην ουρά επανάκλησης, περιμένει μέχρι να γίνει διαθέσιμη η στοίβα κλήσεων, πηγαίνει εκεί, εκτελείται και βλέπουμε Γεια σας από ασύγχρονη επανάκληση στην κονσόλα.
Βρόχος συμβάντων
Το ερώτημα είναι, πώς γνωρίζει το runtime ότι η στοίβα κλήσεων είναι άδεια και πώς καλείται το συμβάν στην ουρά επανάκλησης; Γνωρίστε το βρόχο συμβάντων. Είναι μέρος της μηχανής JS. Αυτή η διαδικασία ελέγχει συνεχώς αν η στοίβα κλήσεων είναι άδεια και, αν είναι, παρακολουθεί αν υπάρχει κάποιο συμβάν στην ουρά επανάκλησης που περιμένει να κληθεί.
Αυτή είναι όλη η μαγεία πίσω από τις σκηνές!
Ανακεφαλαιώνοντας τη θεωρία
Παράλληλη χρήση & παραλληλισμός
Παράλληλη χρήση σημαίνει εκτέλεση πολλαπλών εργασιών ταυτόχρονα, αλλά όχι ταυτόχρονα. Π.χ. δύο εργασίες εκτελούνται σε επικαλυπτόμενες χρονικές περιόδους.
Παραλληλισμός σημαίνει εκτέλεση δύο ή περισσότερων εργασιών ταυτόχρονα, π.χ. εκτέλεση πολλαπλών υπολογισμών ταυτόχρονα.
Νήματα & διεργασίες
Νήματα είναι μια ακολουθία εκτέλεσης κώδικα που μπορεί να εκτελεστεί ανεξάρτητα η μία από την άλλη.
Διαδικασία είναι μια περίπτωση ενός εκτελούμενου προγράμματος. Ένα πρόγραμμα μπορεί να έχει πολλαπλές διεργασίες.
Σύγχρονη & Ασύγχρονη
Στο σύγχρονο προγραμματισμού, οι εργασίες εκτελούνται η μία μετά την άλλη. Κάθε εργασία περιμένει να ολοκληρωθεί κάθε προηγούμενη εργασία και εκτελείται μόνο τότε.
Στο ασύγχρονη προγραμματισμού, όταν εκτελείται μια εργασία, μπορείτε να μεταβείτε σε μια άλλη εργασία χωρίς να περιμένετε να ολοκληρωθεί η προηγούμενη.
Σύγχρονα και ασύγχρονα σε περιβάλλον με ένα και πολλά νήματα
Σύγχρονη με ένα μόνο νήμα: Οι εργασίες εκτελούνται η μία μετά την άλλη. Κάθε εργασία περιμένει να εκτελεστεί η προηγούμενη εργασία.
Σύγχρονη με πολλαπλά νήματα: Οι εργασίες εκτελούνται σε διαφορετικά νήματα, αλλά περιμένουν οποιαδήποτε άλλη εργασία εκτελείται σε οποιοδήποτε άλλο νήμα.
Ασύγχρονη με ένα μόνο νήμα: Οι εργασίες αρχίζουν να εκτελούνται χωρίς να περιμένουν την ολοκλήρωση μιας άλλης εργασίας. Σε μια δεδομένη χρονική στιγμή, μπορεί να εκτελεστεί μόνο μία εργασία.
Ασύγχρονη με πολλαπλά νήματα: Οι εργασίες εκτελούνται σε διαφορετικά νήματα χωρίς να περιμένουν την ολοκλήρωση άλλων εργασιών και ολοκληρώνουν την εκτέλεσή τους ανεξάρτητα.
Ταξινόμηση JavaScript
Αν εξετάσουμε πώς λειτουργούν οι μηχανές JS κάτω από την κουκούλα, μπορούμε να κατατάξουμε την JS ως μια ασύγχρονη και μονόκλωνη διερμηνευμένη γλώσσα. Η λέξη "διερμηνευμένη" είναι πολύ σημαντική επειδή σημαίνει ότι η γλώσσα θα εξαρτάται πάντα από τον χρόνο εκτέλεσης και ποτέ δεν θα είναι τόσο γρήγορη όσο οι μεταγλωττισμένες γλώσσες με ενσωματωμένο multi-threading.
Αξίζει να σημειωθεί ότι ο Node.js μπορεί να επιτύχει πραγματικό multi-threading, υπό την προϋπόθεση ότι κάθε νήμα ξεκινά ως ξεχωριστή διεργασία. Υπάρχουν βιβλιοθήκες γι' αυτό, αλλά το Node.js έχει ένα ενσωματωμένο χαρακτηριστικό που ονομάζεται Νήματα εργαζομένων.
Όλα τα GIFs του βρόχου συμβάντος προέρχονται από το Loupe εφαρμογή που δημιουργήθηκε από τον Philip Roberts, όπου μπορείτε να δοκιμάσετε τα ασύγχρονα σενάριά σας.