Εικονογράφηση του περίφημου προβλήματος

Συναλλαγματικός κύκλος vs βρόχος συμβάντος έναντι βρόχου συμβάντος + συνδρομικότητα

Πρώτα απ 'όλα αφήνει να εξηγηθεί η ορολογία.
Συνδρομικό - Σημαίνει ότι έχετε πολλαπλές ουρές εργασιών σε πολλούς πυρήνες επεξεργαστών / νήματα. Αλλά είναι τελείως διαφορετική από την Παράλληλη εκτέλεση, η παράλληλη εκτέλεση δεν θα περιέχει πολλαπλές λειτουργίες Queue's για παράλληλη περίπτωση θα χρειαστούμε 1 πυρήνα CPU / thread ανά έργο για πλήρη παράλληλη εκτέλεση, η οποία στις περισσότερες περιπτώσεις δεν μπορούμε να καθορίσουμε. Αυτός είναι ο λόγος για τον σύγχρονο προγραμματισμό λογισμικού Ο παράλληλος προγραμματισμός μερικές φορές σημαίνει "Συναλλαγματικότητα", ξέρω ότι είναι παράξενο, αλλά προφανώς είναι αυτό που έχουμε προς το παρόν (εξαρτάται από το OS cpu / thread model).
Loop συμβάντος - Ενεργοποιεί έναν άπειρο κύκλο με ένα απλό νήμα, ο οποίος κάνει μία εργασία κάθε φορά και δεν κάνει μόνο ουρά εργασίας μόνο, αλλά δίνει επίσης προτεραιότητα στις εργασίες, επειδή με το βρόχο συμβάντων έχετε μόνο έναν πόρο για εκτέλεση (1 νήμα) κάποιες εργασίες αμέσως χρειάζεστε την ιεράρχηση των καθηκόντων. Με λίγα λόγια αυτή η προσέγγιση προγραμματισμού αποκαλείται Προγραμματισμός Ασφαλούς Thread, επειδή μόνο μία εργασία / λειτουργία / λειτουργία θα μπορούσε να εκτελεστεί κάθε φορά, και αν αλλάζετε κάτι, θα άλλαζε ήδη κατά την επόμενη εκτέλεση εργασιών.

Συγχρονισμένος προγραμματισμός

Στους σύγχρονους υπολογιστές / servers έχουμε τουλάχιστον 2 πυρήνες CPU και min. 4 σπειρώματα CPU. Αλλά στους διακομιστές τώρα avg. server έχουν τουλάχιστον 16 θέματα CPU. Έτσι, εάν γράφετε λογισμικό που χρειάζεται κάποια απόδοση, θα πρέπει σίγουρα να το εξετάσετε κατά τέτοιο τρόπο ώστε να χρησιμοποιεί όλους τους πυρήνες της CPU που είναι διαθέσιμοι στο διακομιστή.

Αυτή η εικόνα εμφανίζει το βασικό μοντέλο της ταυτόχρονο, αλλά του corse δεν είναι τόσο εύκολο που εμφανίζεται :)

Ο προγραμματισμός συχνοτήτων γίνεται πολύ δύσκολος με κάποιους κοινόχρηστους πόρους, για παράδειγμα, αφήνει να ρίξετε μια ματιά σε αυτό το Go απλό ταυτόχρονο κώδικα.

// Λανθασμένη ταυτότητα με τη γλώσσα Go
κύριο πακέτο
εισαγωγής (
   "fmt"
   "χρόνος"
)
var SharedMap = κάνει (συμβολοσειρά χάρτη [συμβολοσειράς]
func changeMap (συμβολοσειρά τιμής) {
    SharedMap ["test"] = τιμή
}}
func main () {
    go changeMap ("τιμή1")
    go changeMap ("τιμή2")
    time.Sleep (time.Millisecond * 500)
    fmt.Println (SharedMap ["test"])
}}
// Αυτό θα εκτυπώσει "value1" ή "value2" που δεν ξέρουμε ακριβώς!

Σε αυτήν την περίπτωση, η Go θα πυροδοτήσει 2 ταυτόχρονες εργασίες ίσως σε διαφορετικούς πυρήνες CPU και δεν μπορούμε να προβλέψουμε ποιος θα εκτελεστεί πρώτα, οπότε δεν θα γνωρίζουμε τι θα εμφανιστεί στο τέλος.
Γιατί; - Είναι απλό! Προγραμματίζουμε 2 διαφορετικές εργασίες σε διαφορετικούς πυρήνες CPU, αλλά χρησιμοποιούν μία κοινή κοινόχρηστη μεταβλητή / μνήμη, έτσι ώστε και οι δύο να αλλάζουν αυτή τη μνήμη και σε ορισμένες περιπτώσεις αυτό θα ήταν η περίπτωση συντριβής προγράμματος / εξαίρεσης.

Επομένως, για να προβλέψουμε την εκτέλεση προγραμματισμού συγχρονισμού, πρέπει να χρησιμοποιήσουμε ορισμένες λειτουργίες κλειδώματος όπως το Mutex. Με αυτό μπορούμε να κλειδώσουμε αυτόν τον πόρο κοινής μνήμης και να το διαθέσουμε μόνο για μία εργασία κάθε φορά.
Αυτό το στυλ προγραμματισμού αποκαλείται Αποκλεισμός, διότι στην πραγματικότητα αποκλείουμε όλες τις εργασίες έως ότου γίνει η τρέχουσα εργασία με κοινή μνήμη.

Οι περισσότεροι προγραμματιστές δεν τους αρέσει ο ταυτόχρονος προγραμματισμός επειδή η ταυτότητα δεν σημαίνει πάντα απόδοση. Εξαρτάται από συγκεκριμένες περιπτώσεις.

Ενιαίο βρόχο συμβάντων με σπείρωμα

Αυτή η προσέγγιση ανάπτυξης λογισμικού είναι πολύ πιο απλή από τον προγραμματισμό ταυτόχρονων. Επειδή η αρχή είναι πολύ απλή. Έχετε μόνο μία εκτέλεση εργασιών ανά πάσα στιγμή. Και σε αυτή την περίπτωση δεν έχετε κανένα πρόβλημα με τις κοινές μεταβλητές / μνήμη, επειδή το πρόγραμμα είναι πιο προβλέψιμο με μια μεμονωμένη εργασία κάθε φορά.

Η γενική ροή ακολουθεί
1. Εκτροπέας συμβάντων που προσθέτει εργασία στην ουρά Συμβάντων για εκτέλεση σε επόμενο κύκλο βρόχου
2. Εκτέλεση βρόχου συμβάντων από την ουρά συμβάντων και την επεξεργασία τους με βάση τους χειριστές

Αφήνει να γράψει το ίδιο παράδειγμα με το node.js

αφήστε SharedMap = {};
const changeMap = (τιμή) => {
    επιστροφή () => {
        SharedMap ["test"] = τιμή
    }}
}}
// 0 Timeout σημαίνει ότι κάνουμε νέα Task in Queue για τον επόμενο κύκλο
setTimeout (changeMap ("τιμή1"), 0).
setTimeout (changeMap ("τιμή2"), 0);
setTimeout (() => {
   console.log (SharedMap ["test"])
}, 500).
// στην περίπτωση αυτή το Node.js θα εκτυπώσει "value2" επειδή είναι μονό
// threaded και έχει "μόνο μία ουρά εργασίας"

Όπως μπορείτε να φανταστείτε σε αυτήν την περίπτωση κώδικα τρόπο πιο προβλέψιμο από ό, τι με ταυτόχρονη Go παράδειγμα, και αυτό οφείλεται στο γεγονός ότι Node.js εκτελείται σε μια ενιαία λειτουργία με σπείρωμα χρησιμοποιώντας βρόχο συμβάντων JavaScript.

Σε ορισμένες περιπτώσεις, ο βρόχος συμβάντων δίνει περισσότερη απόδοση από ότι με την ταυτότητα, λόγω της μη αποκλειστικής συμπεριφοράς. Πολύ καλό παράδειγμα είναι οι εφαρμογές Δικτύωσης, επειδή χρησιμοποιούν δεδομένα μοναδικής σύνδεσης πόρων δικτύου και επεξεργασίας μόνο όταν είναι διαθέσιμα χρησιμοποιώντας βρόχους συμβάντων ασφαλείας Thread.

Συνυπολογισμός + Βρόχος συμβάντων - Πισίνα με νήμα με ασφάλεια νήματος

Κάνοντας ταυτόχρονες εφαρμογές θα μπορούσε να είναι πολύ δύσκολο, επειδή τα σφάλματα διαφθοράς μνήμης θα ήταν παντού ή απλά η αίτησή σας θα αρχίσει να εμποδίζει τις ενέργειες σε κάθε εργασία. Ειδικά αν θέλετε να έχετε τη μέγιστη απόδοση, πρέπει να συνδυάσετε και τα δύο!

Ας ρίξουμε μια ματιά στο μοντέλο Thread Pool + Loop Model από τη δομή του Nginx Web Server

Η κύρια επεξεργασία δικτύων και ρυθμίσεων πραγματοποιείται από το Worker Loop Event σε ένα μόνο νήμα για ασφάλεια, αλλά όταν το Nginx χρειάζεται να διαβάσει κάποιο αρχείο ή να επεξεργαστεί τις κεφαλίδες / σώμα αιτήματος HTTP, οι οποίες είναι λειτουργίες αποκλεισμού, στέλνει αυτή την εργασία στο Pool Thread του για ταυτόχρονη επεξεργασία. Και όταν ολοκληρωθεί η εργασία, το αποτέλεσμα αποστέλλεται ξανά στον βρόχο συμβάντων για την εκτέλεση ασφαλούς επεξεργασίας με νήμα.

Χρησιμοποιώντας αυτή τη δομή, παίρνετε τόσο την ασφάλεια του νήματος όσο και την συνάφεια, η οποία επιτρέπει να χρησιμοποιείτε όλους τους πυρήνες της CPU για απόδοση και να διατηρείτε την αρχή μη μπλοκαρίσματος με έναν βρόχο συμβάντων με ένα σπείρωμα.

συμπέρασμα

Πολλά λογισμικά γράφονται με καθαρή συνάφεια ή με καθαρό βρόχο συμβάντων μεμονωμένων σπειρωμάτων, αλλά συνδυάζοντας τόσο μέσα από μία ενιαία εφαρμογή, καθιστώντας έτσι πιο εύκολη τη σύνταξη εκτεταμένων εφαρμογών και χρησιμοποιώντας όλους τους διαθέσιμους πόρους CPU.