Εργαλεία συγχρονισμού νημάτων στο λειτουργικό σύστημα Windows (κρίσιμες ενότητες, mutexes, σηματοφόροι, συμβάντα). Αντικείμενα συγχρονισμού στα Windows Συγχρονισμός διαδικασιών με χρήση συμβάντων

Διάλεξη Νο. 9. Συγχρονισμός διεργασιών και νημάτων

1. Στόχοι και μέσα συγχρονισμού.

2. Μηχανισμοί συγχρονισμού.

1.Στόχοι και μέσα συγχρονισμού

Υπάρχει μια αρκετά ευρεία κατηγορία εργαλείων λειτουργικού συστήματος που διασφαλίζουν τον αμοιβαίο συγχρονισμό των διεργασιών και των νημάτων. Η ανάγκη για συγχρονισμό νημάτων προκύπτει μόνο σε ένα λειτουργικό σύστημα πολλαπλών προγραμμάτων και σχετίζεται με την κοινή χρήση υλικού και πόρων πληροφοριών του συστήματος υπολογιστή. Ο συγχρονισμός είναι απαραίτητος για την αποφυγή κούρσας και αδιεξόδου κατά την ανταλλαγή δεδομένων μεταξύ νημάτων, την κοινή χρήση δεδομένων και την πρόσβαση στον επεξεργαστή και τις συσκευές I/O.

Σε πολλά λειτουργικά συστήματα, αυτά τα εργαλεία ονομάζονται εργαλεία επικοινωνίας μεταξύ διεργασιών (IPC), κάτι που αντανακλά την ιστορική υπεροχή της έννοιας της «διαδικασίας» σε σχέση με την έννοια του «νήματος». Συνήθως, τα εργαλεία IPC περιλαμβάνουν όχι μόνο εργαλεία συγχρονισμού διαδικασιών, αλλά και εργαλεία ανταλλαγής δεδομένων μεταξύ διεργασιών.

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


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

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

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

Κατά την κοινή χρήση πόρων υλικού, ο συγχρονισμός είναι επίσης απολύτως απαραίτητος. Όταν, για παράδειγμα, ένα ενεργό νήμα χρειάζεται πρόσβαση σε μια σειριακή θύρα και ένα άλλο νήμα που βρίσκεται αυτήν τη στιγμή σε κατάσταση αναμονής λειτουργεί με αυτήν τη θύρα σε αποκλειστική λειτουργία, το λειτουργικό σύστημα αναστέλλει το ενεργό νήμα και δεν το ενεργοποιεί μέχρι τη θύρα που χρειάζεται ειναι δωρεάν . Συχνά είναι επίσης απαραίτητος ο συγχρονισμός με συμβάντα εξωτερικά του συστήματος υπολογιστή, για παράδειγμα, η αντίδραση στο πάτημα του συνδυασμού πλήκτρων Ctrl+C.

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

Για να συγχρονίσει τα νήματα του προγράμματος εφαρμογών, ο προγραμματιστής μπορεί να χρησιμοποιήσει τόσο τα δικά του εργαλεία και τεχνικές συγχρονισμού όσο και τα εργαλεία του λειτουργικού συστήματος. Για παράδειγμα, δύο νήματα της ίδιας διαδικασίας εφαρμογής μπορούν να συντονίσουν την εργασία τους χρησιμοποιώντας μια καθολική μεταβλητή Boolean διαθέσιμη και για τους δύο, η οποία ορίζεται σε ένα όταν συμβαίνει κάποιο συμβάν, για παράδειγμα, το ένα νήμα παράγει τα δεδομένα που είναι απαραίτητα για να συνεχίσει να λειτουργεί το άλλο. Ωστόσο, σε πολλές περιπτώσεις, οι ευκολίες συγχρονισμού που παρέχει το λειτουργικό σύστημα με τη μορφή κλήσεων συστήματος είναι πιο αποτελεσματικές ή ακόμη και οι μόνες δυνατές. Έτσι, τα νήματα που ανήκουν σε διαφορετικές διεργασίες δεν είναι σε θέση να παρεμβαίνουν με κανέναν τρόπο στη δουλειά του άλλου. Χωρίς τη μεσολάβηση του λειτουργικού συστήματος, δεν μπορούν να αναστείλουν ο ένας τον άλλον ή να ειδοποιήσουν ο ένας τον άλλον για την εμφάνιση ενός συμβάντος. Τα εργαλεία συγχρονισμού χρησιμοποιούνται από το λειτουργικό σύστημα όχι μόνο για τον συγχρονισμό των διαδικασιών εφαρμογής, αλλά και για τις εσωτερικές του ανάγκες.

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


Η ανάγκη για συγχρονισμό και φυλή

Η παραμέληση προβλημάτων συγχρονισμού σε ένα σύστημα πολλαπλών νημάτων μπορεί να οδηγήσει σε εσφαλμένη επίλυση του προβλήματος ή ακόμη και σε διακοπή λειτουργίας του συστήματος. Σκεφτείτε, για παράδειγμα (Εικ. 4.16), το έργο της διατήρησης μιας βάσης δεδομένων με πελάτες μιας συγκεκριμένης επιχείρησης. Σε κάθε πελάτη εκχωρείται μια ξεχωριστή εγγραφή στη βάση δεδομένων, η οποία περιέχει, μεταξύ άλλων πεδίων, τα πεδία Παραγγελία και Πληρωμή. Το πρόγραμμα που διατηρεί τη βάση δεδομένων έχει σχεδιαστεί ως μια ενιαία διαδικασία που έχει πολλά νήματα, συμπεριλαμβανομένου του νήματος Α, το οποίο εισάγει πληροφορίες σχετικά με τις παραγγελίες που λαμβάνονται από πελάτες στη βάση δεδομένων και του νήματος Β, το οποίο καταγράφει στη βάση δεδομένων πληροφορίες σχετικά με τις πληρωμές πελατών για τιμολόγια. Και τα δύο αυτά νήματα συνεργάζονται σε ένα κοινό αρχείο βάσης δεδομένων χρησιμοποιώντας τους ίδιους αλγόριθμους, οι οποίοι περιλαμβάνουν τρία βήματα.

2. Εισαγάγετε μια νέα τιμή στο πεδίο Παραγγελία (για τη ροή Α) ή Πληρωμή (για τη ροή Β).

3. Επιστρέψτε την τροποποιημένη εγγραφή στο αρχείο βάσης δεδομένων.

https://pandia.ru/text/78/239/images/image002_238.gif" width="505" height="374 src=">

Ρύζι. 4.17.Η επίδραση των σχετικών ταχυτήτων ροής στο αποτέλεσμα της επίλυσης του προβλήματος

Κρίσιμο τμήμα

Μια σημαντική έννοια στον συγχρονισμό νημάτων είναι η έννοια ενός «κρίσιμου τμήματος» ενός προγράμματος. Κρίσιμο τμήμαείναι ένα μέρος ενός προγράμματος του οποίου το αποτέλεσμα εκτέλεσης μπορεί να αλλάξει απρόβλεπτα εάν οι μεταβλητές που σχετίζονται με αυτό το τμήμα του προγράμματος αλλάξουν από άλλα νήματα ενώ η εκτέλεση αυτού του τμήματος δεν έχει ακόμη ολοκληρωθεί. Το κρίσιμο τμήμα ορίζεται πάντα σε σχέση με ορισμένα κρίσιμα δεδομέναεάν αλλάξει με ασυντόνιστο τρόπο, μπορεί να εμφανιστούν ανεπιθύμητες ενέργειες. Στο προηγούμενο παράδειγμα, τα κρίσιμα δεδομένα ήταν οι εγγραφές του αρχείου της βάσης δεδομένων. Όλα τα νήματα που λειτουργούν με κρίσιμα δεδομένα πρέπει να έχουν καθορισμένο ένα κρίσιμο τμήμα. Σημειώστε ότι σε διαφορετικά νήματα το κρίσιμο τμήμα αποτελείται γενικά από διαφορετικές ακολουθίες εντολών.

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

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

2. Μηχανισμοί συγχρονισμού.

Αποκλεισμός μεταβλητών

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

Κάτι που θα απενεργοποιούσε τις διακοπές σε όλη τη λειτουργία επαλήθευσης και εγκατάστασης.

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

Στο Σχ. Το Σχήμα 4.19 δείχνει πώς αυτές οι λειτουργίες υλοποιούν τον αμοιβαίο αποκλεισμό στο λειτουργικό σύστημα Windows NT. Πριν ξεκινήσετε την τροποποίηση κρίσιμων δεδομένων, το νήμα εκδίδει την κλήση συστήματος EnterCriticalSection(). Αυτή η κλήση εκτελεί πρώτα, όπως στην προηγούμενη περίπτωση, έναν έλεγχο της μεταβλητής αποκλεισμού που αντικατοπτρίζει την κατάσταση του κρίσιμου πόρου. Εάν η κλήση συστήματος καθορίσει ότι ο πόρος είναι απασχολημένος (F(D) = 0), σε αντίθεση με την προηγούμενη περίπτωση, δεν εκτελεί κυκλική δημοσκόπηση, αλλά βάζει το νήμα σε κατάσταση αναμονής (D) και σημειώνει ότι αυτό το νήμα θα πρέπει να ενεργοποιηθεί, όταν γίνει διαθέσιμος ο αντίστοιχος πόρος. Το νήμα που χρησιμοποιεί αυτήν τη στιγμή αυτόν τον πόρο, μετά την έξοδο από την κρίσιμη ενότητα, πρέπει να εκτελέσει τη συνάρτηση συστήματος LeaveCriticalSectionO, ως αποτέλεσμα της οποίας η μεταβλητή αποκλεισμού παίρνει την τιμή που αντιστοιχεί στην ελεύθερη κατάσταση του πόρου (F(D) = 1). και το λειτουργικό σύστημα εξετάζει την ουρά όσων περιμένουν αυτά τα νήματα πόρων και μετακινεί το πρώτο νήμα από την ουρά στην κατάσταση ετοιμότητας.

Γενικά κόστη" href="/text/category/nakladnie_rashodi/" rel="bookmark">Οι γενικές δαπάνες λειτουργικού συστήματος για την υλοποίηση της λειτουργίας εισόδου και εξόδου από το κρίσιμο τμήμα ενδέχεται να υπερβαίνουν την εξοικονόμηση που επιτυγχάνεται.

Σηματοφόροι

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

Για να δουλέψουμε με σηματοφόρους, εισάγονται δύο πρωτόγονα, που παραδοσιακά συμβολίζονται με P και V. Έστω η μεταβλητή S αντιπροσωπεύει έναν σηματοφόρο. Στη συνέχεια οι ενέργειες V(S) και P(S) ορίζονται ως εξής.

* V(S): Η μεταβλητή S αυξάνεται κατά 1 ως μία ενέργεια. Η δειγματοληψία, η κατασκευή και η αποθήκευση δεν μπορούν να διακοπούν. Η μεταβλητή S δεν είναι προσβάσιμη από άλλα νήματα ενώ εκτελείται αυτή η λειτουργία.

* P(S): Μειώνει το S κατά 1 αν είναι δυνατόν. Εάν 5=0 και είναι αδύνατο να μειωθεί το S ενώ παραμένει στην περιοχή των μη αρνητικών ακέραιων τιμών, τότε η λειτουργία P που καλεί το νήμα περιμένει μέχρι να καταστεί δυνατή αυτή η μείωση. Ένας επιτυχημένος έλεγχος και μείωση είναι επίσης μια αδιαίρετη πράξη.

Δεν επιτρέπονται διακοπές κατά την εκτέλεση των αρχικών V και P.

Στην ειδική περίπτωση όπου ο σηματοφόρος S μπορεί να πάρει μόνο τις τιμές 0 και 1, γίνεται μια μεταβλητή αποκλεισμού, η οποία για το λόγο αυτό ονομάζεται συχνά δυαδικός σηματοφόρος. Η δραστηριότητα P έχει τη δυνατότητα να θέσει το νήμα που το εκτελεί σε κατάσταση αναμονής, ενώ η δραστηριότητα V μπορεί, υπό ορισμένες συνθήκες, να ξυπνήσει ένα άλλο νήμα που ανεστάλη από τη δραστηριότητα P.

Ας δούμε τη χρήση των σηματοφόρων χρησιμοποιώντας ένα κλασικό παράδειγμα της αλληλεπίδρασης δύο νημάτων που εκτελούνται σε λειτουργία πολλαπλού προγραμματισμού, το ένα εκ των οποίων γράφει δεδομένα στο buffer pool και το άλλο τα διαβάζει από το buffer pool. Αφήστε το buffer pool να αποτελείται από N buffer, καθένα από τα οποία μπορεί να περιέχει μία καταχώρηση. Γενικά, το νήμα εγγραφής και το νήμα ανάγνωσης μπορούν να έχουν διαφορετικές ταχύτητες και να έχουν πρόσβαση στο buffer pool με διαφορετική ένταση. Σε μια περίοδο, η ταχύτητα εγγραφής μπορεί να υπερβαίνει την ταχύτητα ανάγνωσης, σε μια άλλη - το αντίστροφο. Για να συνεργαστεί σωστά, το νήμα εγγραφής πρέπει να σταματήσει όταν όλα τα buffer είναι απασχολημένα και να αφυπνιστεί όταν ελευθερωθεί τουλάχιστον ένα buffer. Αντίθετα, το νήμα του αναγνώστη θα πρέπει να σταματήσει όταν όλα τα buffer είναι άδεια και να αφυπνιστεί όταν εμφανιστεί τουλάχιστον μία εγγραφή.

Ας εισαγάγουμε δύο σηματοφόρους: e - τον αριθμό των κενών buffers, και f - τον αριθμό των γεμισμένων buffers, και στην αρχική κατάσταση e = N, a f = 0. Στη συνέχεια, η λειτουργία των νημάτων με ένα κοινό buffer pool μπορεί να περιγραφεί ως εξής (Εικ. 4.20).

Το νήμα εγγραφής εκτελεί πρώτα μια λειτουργία P(e), με την οποία ελέγχει εάν υπάρχουν κενά buffers στο buffer pool. Σύμφωνα με τη σημασιολογία της πράξης P, εάν ο σηματοφόρος e είναι ίσος με 0 (δηλαδή, δεν υπάρχουν ελεύθερα buffer αυτή τη στιγμή), τότε το νήμα εγγραφής εισέρχεται σε κατάσταση αναμονής. Εάν η τιμή του e είναι θετικός αριθμός, τότε μειώνει τον αριθμό των ελεύθερων buffer, γράφει δεδομένα στην επόμενη ελεύθερη προσωρινή μνήμη και στη συνέχεια αυξάνει τον αριθμό των κατειλημμένων buffer με τη λειτουργία V(f). Το νήμα του αναγνώστη λειτουργεί με παρόμοιο τρόπο, με τη διαφορά ότι ξεκινά ελέγχοντας για πλήρη buffers και μετά την ανάγνωση των δεδομένων, αυξάνει τον αριθμό των ελεύθερων buffer.

DIV_ADBLOCK860">

Ένας σηματοφόρος μπορεί επίσης να χρησιμοποιηθεί ως μεταβλητή αποκλεισμού. Στο παράδειγμα που συζητήθηκε παραπάνω, προκειμένου να εξαλειφθούν οι συγκρούσεις κατά την εργασία με μια κοινόχρηστη περιοχή μνήμης, θα υποθέσουμε ότι η εγγραφή και η ανάγνωση από το buffer είναι κρίσιμα τμήματα. Θα διασφαλίσουμε τον αμοιβαίο αποκλεισμό χρησιμοποιώντας το δυαδικό σηματοφόρο b (Εικ. 4.21). Και τα δύο νήματα, αφού ελέγξουν τη διαθεσιμότητα των buffer, πρέπει να ελέγξουν τη διαθεσιμότητα του κρίσιμου τμήματος.

https://pandia.ru/text/78/239/images/image007_110.jpg" width="495" height="639 src=">

Ρύζι. 4.22.Η εμφάνιση αδιεξόδων κατά την εκτέλεση του προγράμματος

ΣΗΜΕΙΩΣΗ

Τα αδιέξοδα πρέπει να διακρίνονται από τις απλές ουρές, αν και και τα δύο προκύπτουν όταν μοιράζονται οι πόροι και μοιάζουν με την εμφάνιση: ένα νήμα αναστέλλεται και περιμένει να απελευθερωθεί ένας πόρος. Ωστόσο, μια ουρά είναι ένα φυσιολογικό φαινόμενο, ένα εγγενές σημάδι υψηλής χρήσης πόρων όταν τα αιτήματα φτάνουν τυχαία. Μια ουρά εμφανίζεται όταν ένας πόρος δεν είναι διαθέσιμος αυτήν τη στιγμή, αλλά θα απελευθερωθεί μετά από κάποιο χρονικό διάστημα, επιτρέποντας στο νήμα να συνεχίσει την εκτέλεση. Ένα αδιέξοδο, όπως υποδηλώνει το όνομά του, είναι μια κάπως άλυτη κατάσταση. Απαραίτητη προϋπόθεση για να συμβεί ένα αδιέξοδο είναι ότι ένα νήμα χρειάζεται πολλούς πόρους ταυτόχρονα.

Στα παραδείγματα που εξετάστηκαν, το αδιέξοδο σχηματίστηκε από δύο νήματα, αλλά περισσότερα νήματα μπορούν να μπλοκάρουν αμοιβαία το ένα το άλλο. Στο Σχ. Το σχήμα 2.23 δείχνει μια τέτοια κατανομή πόρων Ri μεταξύ πολλών νημάτων Tj, η οποία οδήγησε στην εμφάνιση αδιεξόδων. Τα βέλη υποδεικνύουν τις απαιτήσεις πόρων της ροής. Ένα συμπαγές βέλος σημαίνει ότι ο αντίστοιχος πόρος έχει εκχωρηθεί στο νήμα και ένα διακεκομμένο βέλος συνδέει το νήμα με τον πόρο που χρειάζεται, αλλά δεν μπορεί ακόμη να εκχωρηθεί επειδή καταλαμβάνεται από άλλο νήμα. Για παράδειγμα, το νήμα T1 χρειάζεται πόρους R1 και R2 για να εκτελέσει εργασία, από τους οποίους έχει εκχωρηθεί μόνο ένας - R1, και ο πόρος R2 διατηρείται από το νήμα T2. Κανένα από τα τέσσερα νήματα που φαίνονται στο σχήμα δεν μπορεί να συνεχίσει την εργασία του, καθώς δεν διαθέτει όλους τους απαραίτητους πόρους για αυτό.

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

Ο ιδιοκτήτης" href="/text/category/vladeletc/" rel="bookmark">ο κάτοχος, τον θέτει σε κατάσταση χωρίς σήμα και εισέρχεται στην κρίσιμη ενότητα. Αφού το νήμα ολοκληρώσει την εργασία με τα κρίσιμα δεδομένα, "δίνει επάνω" το mutex, ρυθμίζοντάς το σε κατάσταση σηματοδότησης. Αυτή τη στιγμή, το mutex είναι ελεύθερο και δεν ανήκει σε κανένα νήμα. Εάν κάποιο νήμα περιμένει να απελευθερωθεί, τότε γίνεται ο επόμενος κάτοχος αυτού του mutex, στο την ίδια στιγμή το mutex μεταβαίνει σε μη σηματοδοτημένη κατάσταση.

Ένα αντικείμενο συμβάντος (σε αυτήν την περίπτωση η λέξη "γεγονός" χρησιμοποιείται με στενή έννοια, ως προσδιορισμός ενός συγκεκριμένου τύπου αντικειμένου συγχρονισμού) συνήθως χρησιμοποιείται όχι για πρόσβαση σε δεδομένα, αλλά για ειδοποίηση άλλων νημάτων ότι ορισμένες ενέργειες έχουν ολοκληρωθεί. Ας, για παράδειγμα, σε κάποια εφαρμογή, η εργασία είναι οργανωμένη με τέτοιο τρόπο ώστε ένα νήμα να διαβάζει δεδομένα από ένα αρχείο σε ένα buffer μνήμης και άλλα νήματα να επεξεργάζονται αυτά τα δεδομένα, μετά το πρώτο νήμα διαβάζει ένα νέο τμήμα δεδομένων και άλλα νήματα επεξεργαστείτε το ξανά και ούτω καθεξής. Στην αρχή της εκτέλεσης, το πρώτο νήμα θέτει το αντικείμενο συμβάντος σε κατάσταση χωρίς σήμα. Όλα τα άλλα νήματα έχουν κάνει κλήση στο Wait(X), όπου το X είναι ένας δείκτης συμβάντος και βρίσκονται σε κατάσταση αναστολής, περιμένοντας να συμβεί αυτό το συμβάν. Μόλις το buffer γεμίσει, το πρώτο νήμα το αναφέρει στο λειτουργικό σύστημα καλώντας το Set(X). Το λειτουργικό σύστημα σαρώνει την ουρά των νημάτων αναμονής και ενεργοποιεί τυχόν νήματα που περιμένουν για αυτό το συμβάν.

σήματα

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

Ένα παράδειγμα ασύγχρονου σήματος είναι ένα σήμα από ένα τερματικό. Πολλά λειτουργικά συστήματα προβλέπουν την άμεση αφαίρεση μιας διεργασίας από την εκτέλεση. Για να γίνει αυτό, ο χρήστης μπορεί να πατήσει έναν συγκεκριμένο συνδυασμό πλήκτρων (Ctrl+C, Ctrl+Break), ως αποτέλεσμα του οποίου το λειτουργικό σύστημα παράγει ένα σήμα και το στέλνει στην ενεργή διαδικασία. Το σήμα μπορεί να φτάσει οποιαδήποτε στιγμή κατά την εκτέλεση μιας διεργασίας (δηλαδή είναι ασύγχρονη), απαιτώντας την άμεση τερματισμό της διαδικασίας. Σε αυτή την περίπτωση, η απόκριση στο σήμα είναι η άνευ όρων ολοκλήρωση της διαδικασίας.

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

Τα σήματα παρέχουν λογική επικοινωνία μεταξύ διεργασιών και μεταξύ διεργασιών και χρηστών (τερματικά). Εφόσον η αποστολή ενός σήματος απαιτεί γνώση του αναγνωριστικού διεργασίας, η αλληλεπίδραση μέσω σημάτων είναι δυνατή μόνο μεταξύ σχετικών διεργασιών που μπορούν να λάβουν πληροφορίες η μία για τα αναγνωριστικά της άλλης.

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

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

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

Δεδομένου ότι υπάρχουν σχεδόν πάντα πολλά περισσότερα νήματα από ό,τι υπάρχουν φυσικοί επεξεργαστές για την εκτέλεσή τους, τα νήματα δεν εκτελούνται στην πραγματικότητα ταυτόχρονα, αλλά με τη σειρά τους (ο χρόνος του επεξεργαστή κατανέμεται μεταξύ των νημάτων). Αλλά η εναλλαγή μεταξύ τους συμβαίνει τόσο συχνά που φαίνεται να τρέχουν παράλληλα.

Ανάλογα με την κατάσταση, τα νήματα μπορεί να είναι σε τρεις καταστάσεις. Πρώτον, ένα νήμα μπορεί να εκτελεστεί όταν του εκχωρηθεί χρόνος CPU, δηλ. μπορεί να βρίσκεται σε κατάσταση δραστηριότητας. Δεύτερον, μπορεί να είναι ανενεργό και να περιμένει να εκχωρηθεί ο επεξεργαστής, π.χ. να είναι σε κατάσταση ετοιμότητας. Και υπάρχει μια τρίτη, επίσης πολύ σημαντική κατάσταση - η κατάσταση αποκλεισμού. Όταν ένα νήμα είναι αποκλεισμένο, δεν εκχωρείται καθόλου. Συνήθως, τοποθετείται ένα μπλοκ ενώ περιμένει κάποιο συμβάν. Όταν συμβεί αυτό το συμβάν, το νήμα μετακινείται αυτόματα από την κατάσταση αποκλεισμού στην κατάσταση ετοιμότητας. Για παράδειγμα, εάν το ένα νήμα εκτελεί υπολογισμούς και το άλλο πρέπει να περιμένει τα αποτελέσματα για να τα αποθηκεύσει στο δίσκο. Το δεύτερο θα μπορούσε να χρησιμοποιήσει έναν βρόχο όπως "while(!isCalcFinished) συνεχίζει;", αλλά είναι εύκολο να επαληθευτεί στην πράξη ότι κατά την εκτέλεση αυτού του βρόχου ο επεξεργαστής είναι 100% απασχολημένος (αυτό ονομάζεται ενεργή αναμονή). Τέτοιοι κύκλοι θα πρέπει να αποφεύγονται εάν είναι δυνατόν, στους οποίους ο μηχανισμός ασφάλισης παρέχει ανεκτίμητη βοήθεια. Το δεύτερο νήμα μπορεί να αποκλειστεί έως ότου το πρώτο νήμα εμφανίσει ένα συμβάν που υποδεικνύει ότι η ανάγνωση έχει ολοκληρωθεί.

Συγχρονισμός νημάτων στο λειτουργικό σύστημα Windows

Τα Windows εφαρμόζουν προληπτικές πολλαπλές εργασίες - αυτό σημαίνει ότι ανά πάσα στιγμή το σύστημα μπορεί να διακόψει την εκτέλεση ενός νήματος και να μεταφέρει τον έλεγχο σε άλλο. Προηγουμένως, στα Windows 3.1, χρησιμοποιήθηκε μια μέθοδος οργάνωσης που ονομάζεται cooperative multitasking: το σύστημα περίμενε μέχρι το ίδιο το νήμα να μεταφέρει τον έλεγχο σε αυτό και γι' αυτό, εάν μια εφαρμογή παγώσει, ο υπολογιστής έπρεπε να επανεκκινηθεί.

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

Παράδειγμα. Νήματα εκτός συγχρονισμού: Εάν διακόψετε προσωρινά το νήμα εξόδου (παύση), το νήμα του πληθυσμού του πίνακα παρασκηνίου θα συνεχίσει να εκτελείται.

#περιλαμβάνω #περιλαμβάνω int a? ΛΑΒΗ hThr; ανυπόγραφο μακρύ uThrID. void Thread(void* pParams) ( int i, num = 0; while (1) ( for (i=0; i<5; i++) a[i] = num; num++; } } int main(void) { hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) printf("%d %d %d %d %d\n", a, a, a, a, a); return 0; }

Αυτός είναι ο λόγος για τον οποίο απαιτείται ένας μηχανισμός που θα επιτρέπει στα νήματα να συντονίζουν την εργασία τους με κοινόχρηστους πόρους. Αυτός ο μηχανισμός ονομάζεται μηχανισμός συγχρονισμού νημάτων.

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

Υπάρχουν πολλά αντικείμενα συγχρονισμού, τα πιο σημαντικά από τα οποία είναι το mutex, το κρίσιμο τμήμα, το συμβάν και ο σηματοφόρος. Κάθε ένα από αυτά τα αντικείμενα εφαρμόζει τη δική του μέθοδο συγχρονισμού. Επίσης, οι διεργασίες και τα νήματα μπορούν να χρησιμοποιηθούν ως αντικείμενα συγχρονισμού (όταν ένα νήμα περιμένει να ολοκληρωθεί ένα άλλο νήμα ή διεργασία). καθώς και αρχεία, συσκευές επικοινωνίας, είσοδος κονσόλας και ειδοποιήσεις αλλαγών.

Οποιοδήποτε αντικείμενο συγχρονισμού μπορεί να βρίσκεται στη λεγόμενη κατάσταση σήματος. Για κάθε τύπο αντικειμένου αυτή η κατάσταση έχει διαφορετική σημασία. Τα νήματα μπορούν να ελέγξουν την τρέχουσα κατάσταση ενός αντικειμένου ή/και να περιμένουν μια αλλαγή σε αυτήν την κατάσταση και έτσι να συντονίσουν τις ενέργειές τους. Αυτό διασφαλίζει ότι όταν ένα νήμα λειτουργεί με αντικείμενα συγχρονισμού (τα δημιουργεί, αλλάζει κατάσταση), το σύστημα δεν θα διακόψει την εκτέλεσή του μέχρι να ολοκληρώσει αυτήν την ενέργεια. Έτσι, όλες οι τελικές πράξεις με αντικείμενα συγχρονισμού είναι ατομικές (αδιαίρετες.

Εργασία με αντικείμενα συγχρονισμού

Για να δημιουργήσετε ένα ή άλλο αντικείμενο συγχρονισμού, καλείται μια ειδική συνάρτηση WinAPI τύπου Create... (για παράδειγμα, CreateMutex). Αυτή η κλήση επιστρέφει μια λαβή σε ένα αντικείμενο (HANDLE) που μπορεί να χρησιμοποιηθεί από όλα τα νήματα που ανήκουν σε αυτή τη διαδικασία. Είναι δυνατή η πρόσβαση σε ένα αντικείμενο συγχρονισμού από άλλη διεργασία - είτε κληρονομώντας μια λαβή σε αυτό το αντικείμενο είτε, κατά προτίμηση, χρησιμοποιώντας μια κλήση στη συνάρτηση ανοίγματος του αντικειμένου (Άνοιγμα...). Μετά από αυτήν την κλήση, η διαδικασία θα λάβει μια λαβή που μπορεί αργότερα να χρησιμοποιηθεί για να εργαστεί με το αντικείμενο. Σε ένα αντικείμενο, εκτός εάν προορίζεται να χρησιμοποιηθεί σε μια ενιαία διαδικασία, πρέπει να δοθεί ένα όνομα. Τα ονόματα όλων των αντικειμένων πρέπει να είναι διαφορετικά (ακόμα και αν είναι διαφορετικών τύπων). Για παράδειγμα, δεν μπορείτε να δημιουργήσετε ένα συμβάν και έναν σηματοφόρο με το ίδιο όνομα.

Χρησιμοποιώντας τον υπάρχοντα περιγραφέα ενός αντικειμένου, μπορείτε να προσδιορίσετε την τρέχουσα κατάστασή του. Αυτό γίνεται χρησιμοποιώντας το λεγόμενο. εκκρεμείς λειτουργίες. Η πιο συχνά χρησιμοποιούμενη συνάρτηση είναι WaitForSingleObject. Αυτή η συνάρτηση παίρνει δύο παραμέτρους, η πρώτη από τις οποίες είναι η λαβή αντικειμένου και η δεύτερη το χρονικό όριο σε ms. Η συνάρτηση επιστρέφει WAIT_OBJECT_0 εάν το αντικείμενο έχει σηματοδοτηθεί, WAIT_TIMEOUT εάν έληξε και WAIT_ABANDONED εάν το αντικείμενο mutex δεν ελευθερώθηκε πριν την έξοδο από το νήμα που ανήκει. Εάν το χρονικό όριο οριστεί ως μηδέν, η συνάρτηση επιστρέφει το αποτέλεσμα αμέσως, διαφορετικά περιμένει για το καθορισμένο χρονικό διάστημα. Εάν η κατάσταση του αντικειμένου γίνει σήμα πριν λήξει αυτή η ώρα, η συνάρτηση θα επιστρέψει WAIT_OBJECT_0, διαφορετικά η συνάρτηση θα επιστρέψει WAIT_TIMEOUT. Εάν η συμβολική σταθερά INFINITE οριστεί ως χρόνος, η συνάρτηση θα περιμένει επ' αόριστον έως ότου η κατάσταση του αντικειμένου γίνει σήμα.

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

Κρίσιμες ενότητες

Ένα αντικείμενο κρίσιμης ενότητας βοηθά τον προγραμματιστή να απομονώσει το τμήμα του κώδικα όπου ένα νήμα έχει πρόσβαση σε έναν κοινόχρηστο πόρο και να αποτρέψει την ταυτόχρονη χρήση του πόρου. Πριν χρησιμοποιήσετε τον πόρο, το νήμα εισέρχεται στην κρίσιμη ενότητα (καλεί τη συνάρτηση EnterCriticalSection). Εάν οποιοδήποτε άλλο νήμα προσπαθήσει να εισέλθει στην ίδια κρίσιμη ενότητα, η εκτέλεσή του θα σταματήσει έως ότου το πρώτο νήμα εγκαταλείψει την ενότητα καλώντας το LeaveCriticalSection. Χρησιμοποιείται μόνο για νήματα μιας διαδικασίας. Η σειρά εισαγωγής στο κρίσιμο τμήμα δεν έχει καθοριστεί.

Υπάρχει επίσης μια συνάρτηση TryEnterCriticalSection που ελέγχει εάν το κρίσιμο τμήμα είναι κατειλημμένο αυτήν τη στιγμή. Με τη βοήθειά του, το νήμα, ενώ περιμένει την πρόσβαση σε έναν πόρο, δεν μπορεί να αποκλειστεί, αλλά να εκτελέσει ορισμένες χρήσιμες ενέργειες.

Παράδειγμα. Συγχρονισμός νημάτων με χρήση κρίσιμων τμημάτων.

#περιλαμβάνω #περιλαμβάνω CRITICAL_SECTION cs; int a? ΛΑΒΗ hThr; ανυπόγραφο μακρύ uThrID. void Thread(void* pParams) ( int i, num = 0; while (1) ( EnterCriticalSection(&cs); for (i=0; i<5; i++) a[i] = num; num++; LeaveCriticalSection(&cs); } } int main(void) { InitializeCriticalSection(&cs); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { EnterCriticalSection(&cs); printf("%d %d %d %d %d\n", a, a, a, a, a); LeaveCriticalSection(&cs); } return 0; }

Αμοιβαίοι αποκλεισμοί

Τα αντικείμενα αμοιβαίας εξαίρεσης (mutexes, mutex - από την ΑΜΟΙΒΑΙΑ ΕΞΑΙΡΕΣΗ) σάς επιτρέπουν να συντονίζετε τον αμοιβαίο αποκλεισμό της πρόσβασης σε έναν κοινόχρηστο πόρο. Η κατάσταση σήματος ενός αντικειμένου (δηλαδή η κατάσταση "set") αντιστοιχεί σε ένα χρονικό σημείο που το αντικείμενο δεν ανήκει σε κανένα νήμα και μπορεί να "αιχμαλωτιστεί". Αντίθετα, η κατάσταση "επαναφοράς" (μη σήματος) αντιστοιχεί στη στιγμή που κάποιο νήμα κατέχει ήδη αυτό το αντικείμενο. Η πρόσβαση σε ένα αντικείμενο παραχωρείται όταν το νήμα που κατέχει το αντικείμενο το απελευθερώσει.

Δύο (ή περισσότερα) νήματα μπορούν να δημιουργήσουν ένα mutex με το ίδιο όνομα καλώντας τη συνάρτηση CreateMutex. Το πρώτο νήμα δημιουργεί στην πραγματικότητα ένα mutex και τα επόμενα παίρνουν μια λαβή σε ένα ήδη υπάρχον αντικείμενο. Αυτό επιτρέπει σε πολλά νήματα να αποκτήσουν μια λαβή στο ίδιο mutex, απαλλάσσοντας τον προγραμματιστή από το να χρειάζεται να ανησυχεί για το ποιος πραγματικά δημιουργεί το mutex. Εάν χρησιμοποιείται αυτή η προσέγγιση, συνιστάται να ορίσετε τη σημαία bInitialOwner σε FALSE, διαφορετικά θα υπάρξει κάποια δυσκολία στον προσδιορισμό του πραγματικού δημιουργού του mutex.

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

  • Μια θυγατρική διεργασία που δημιουργήθηκε χρησιμοποιώντας τη συνάρτηση CreateProcess μπορεί να κληρονομήσει μια λαβή mutex εάν η παράμετρος lpMutexAttributes καθορίστηκε κατά τη δημιουργία του mutex με τη συνάρτηση CreateMutex.
  • Ένα νήμα μπορεί να αποκτήσει ένα αντίγραφο ενός υπάρχοντος mutex χρησιμοποιώντας τη συνάρτηση DuplicateHandle.
  • Ένα νήμα μπορεί να καθορίσει το όνομα ενός υπάρχοντος mutex κατά την κλήση των συναρτήσεων OpenMutex ή CreateMutex.

Για να δηλώσετε ότι μια αμοιβαία εξαίρεση ανήκει στο τρέχον νήμα, πρέπει να καλέσετε μία από τις συναρτήσεις αναμονής. Το νήμα που κατέχει το αντικείμενο μπορεί να το επανακτήσει όσες φορές θέλει (αυτό δεν θα οδηγήσει σε αυτοκλείδωμα), αλλά πρέπει να το απελευθερώσει ίδιες φορές χρησιμοποιώντας τη συνάρτηση ReleaseMutex.

Για να συγχρονίσετε τα νήματα μιας διαδικασίας, είναι πιο αποτελεσματικό να χρησιμοποιείτε κρίσιμα τμήματα.

Παράδειγμα. Συγχρονισμός νημάτων με χρήση mutexes.

#περιλαμβάνω #περιλαμβάνω ΛΑΒΗ hMutex; int a? ΛΑΒΗ hThr; ανυπόγραφο μακρύ uThrID. void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hMutex, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseMutex(hMutex); } } int main(void) { hMutex=CreateMutex(NULL, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hMutex, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseMutex(hMutex); } return 0; }

Εκδηλώσεις

Τα αντικείμενα συμβάντος χρησιμοποιούνται για να ειδοποιούν τα νήματα σε αναμονή ότι έχει συμβεί ένα συμβάν. Υπάρχουν δύο τύποι συμβάντων - με χειροκίνητη και αυτόματη επαναφορά. Η μη αυτόματη επαναφορά εκτελείται από τη λειτουργία ResetEvent. Τα συμβάντα μη αυτόματης επαναφοράς χρησιμοποιούνται για την ταυτόχρονη ειδοποίηση πολλών νημάτων. Όταν χρησιμοποιείτε ένα συμβάν αυτόματης επαναφοράς, μόνο ένα νήμα αναμονής θα λάβει την ειδοποίηση και θα συνεχίσει να εκτελεί, ενώ το υπόλοιπο θα συνεχίσει να περιμένει.

Η συνάρτηση CreateEvent δημιουργεί ένα αντικείμενο συμβάντος, SetEvent - ορίζει το συμβάν στην κατάσταση σήματος, ResetEvent - επαναφέρει το συμβάν. Η συνάρτηση PulseEvent ορίζει ένα συμβάν και αφού συνεχιστούν τα νήματα που περιμένουν αυτό το συμβάν (όλα με μη αυτόματη επαναφορά και μόνο ένα με αυτόματη επαναφορά), το επαναφέρει. Εάν δεν υπάρχουν νήματα αναμονής, το PulseEvent απλώς επαναφέρει το συμβάν.

Παράδειγμα. Συγχρονισμός νημάτων με χρήση συμβάντων.

#περιλαμβάνω #περιλαμβάνω ΧΕΙΡΙΣΜΟΣ hEvent1, hEvent2; int a? ΛΑΒΗ hThr; ανυπόγραφο μακρύ uThrID. void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hEvent2, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; SetEvent(hEvent1); } } int main(void) { hEvent1=CreateEvent(NULL, FALSE, TRUE, NULL); hEvent2=CreateEvent(NULL, FALSE, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hEvent1, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); SetEvent(hEvent2); } return 0; }

Σηματοφόροι

Ένα αντικείμενο σηματοφόρου είναι στην πραγματικότητα ένα αντικείμενο mutex με μετρητή. Αυτό το αντικείμενο επιτρέπει στον εαυτό του να «συλληφθεί» από έναν ορισμένο αριθμό νημάτων. Μετά από αυτό, η «σύλληψη» θα είναι αδύνατη έως ότου ένα από τα νήματα που προηγουμένως «αιχμαλώτισαν» τον σηματοδότη το απελευθερώσει. Οι σηματοφόροι χρησιμοποιούνται για τον περιορισμό του αριθμού των νημάτων που λειτουργούν ταυτόχρονα με έναν πόρο. Ο μέγιστος αριθμός νημάτων μεταφέρεται στο αντικείμενο κατά την αρχικοποίηση· μετά από κάθε «σύλληψη», ο μετρητής σηματοφόρου μειώνεται. Η κατάσταση του σήματος αντιστοιχεί σε μια τιμή μετρητή μεγαλύτερη από το μηδέν. Όταν ο μετρητής είναι μηδέν, ο σηματοφόρος θεωρείται ότι δεν έχει εγκατασταθεί (επαναφέρεται).

Η συνάρτηση CreateSemaphore δημιουργεί ένα αντικείμενο σηματοφόρο που υποδεικνύει τη μέγιστη δυνατή αρχική τιμή του, OpenSemaphore - επιστρέφει έναν περιγραφέα ενός υπάρχοντος σηματοφόρου, ο σηματοφορέας καταγράφεται χρησιμοποιώντας συναρτήσεις αναμονής και η τιμή του σηματοφορέα μειώνεται κατά ένα, ReleaseSemaphore - ο σηματοφορέας απελευθερώνεται με τον σηματοφόρο τιμή αυξημένη κατά την τιμή που καθορίζεται στον αριθμό παραμέτρου.

Παράδειγμα. Συγχρονισμός νημάτων χρησιμοποιώντας σηματοφόρους.

#περιλαμβάνω #περιλαμβάνω ΛΑΒΗ hSem; int a? ΛΑΒΗ hThr; ανυπόγραφο μακρύ uThrID. void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hSem, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseSemaphore(hSem, 1, NULL); } } int main(void) { hSem=CreateSemaphore(NULL, 1, 1, "MySemaphore1"); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hSem, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseSemaphore(hSem, 1, NULL); } return 0; }

Προστατευμένη πρόσβαση σε μεταβλητές

Υπάρχει ένας αριθμός συναρτήσεων που σας επιτρέπουν να εργάζεστε με καθολικές μεταβλητές από όλα τα νήματα χωρίς να ανησυχείτε για το συγχρονισμό, επειδή αυτές οι συναρτήσεις το παρακολουθούν μόνες τους - η εκτέλεσή τους είναι ατομική. Αυτές οι λειτουργίες είναι InterlockedIncrement, InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd και InterlockedCompareExchange. Για παράδειγμα, η συνάρτηση InterlockedIncrement αυξάνει ατομικά την τιμή μιας μεταβλητής 32 bit κατά ένα, κάτι που είναι βολικό στη χρήση για διάφορους μετρητές.

Για να λάβετε πλήρεις πληροφορίες σχετικά με το σκοπό, τη χρήση και τη σύνταξη όλων των λειτουργιών του WIN32 API, πρέπει να χρησιμοποιήσετε το σύστημα βοήθειας MS SDK που περιλαμβάνεται στα περιβάλλοντα προγραμματισμού Borland Delphi ή CBuilder, καθώς και το MSDN, που παρέχεται ως μέρος του συστήματος προγραμματισμού Visual C.

Τα νήματα μπορούν να βρίσκονται σε μία από πολλές καταστάσεις:

    Ετοιμος(έτοιμο) – βρίσκεται στη δεξαμενή των νημάτων που αναμένουν εκτέλεση.

    Τρέξιμο(εκτέλεση) - τρέχει στον επεξεργαστή.

    Αναμονή(αναμονή), που ονομάζεται επίσης idle ή suspended, suspended - σε κατάσταση αναμονής, η οποία τελειώνει με το νήμα που αρχίζει να εκτελείται (κατάσταση εκτέλεσης) ή εισέρχεται στην κατάσταση Ετοιμος;

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

Συγχρονισμός νημάτων

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

Συγχρονισμός νημάτων ( Νήμα συγχρονισμός) είναι ένας γενικός όρος που αναφέρεται στη διαδικασία αλληλεπίδρασης και διασύνδεσης των νημάτων. Λάβετε υπόψη ότι ο συγχρονισμός των νημάτων απαιτεί το ίδιο το λειτουργικό σύστημα να λειτουργεί ως ενδιάμεσος. Τα νήματα δεν μπορούν να αλληλεπιδράσουν μεταξύ τους χωρίς τη συμμετοχή της.

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

Κρίσιμες ενότητες

Μία μέθοδος για τον συγχρονισμό των νημάτων είναι η χρήση κρίσιμων τμημάτων. Αυτή είναι η μόνη μέθοδος συγχρονισμού νημάτων που δεν απαιτεί τον πυρήνα των Windows. (Το κρίσιμο τμήμα δεν είναι αντικείμενο πυρήνα.) Ωστόσο, αυτή η μέθοδος μπορεί να χρησιμοποιηθεί μόνο για τον συγχρονισμό των νημάτων μιας μεμονωμένης διαδικασίας.

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

Πριν χρησιμοποιήσετε μια κρίσιμη ενότητα, πρέπει να την αρχικοποιήσετε χρησιμοποιώντας τη διαδικασία Win32 API InitializeCriticalSection(), η οποία ορίζεται (στους Delphi) ως εξής:

διαδικασία InitializeCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Η παράμετρος IpCriticalSection είναι μια εγγραφή τύπου TRTLCriticalSection που μεταβιβάζεται με αναφορά. Ο ακριβής ορισμός της καταχώρισης TRTLCriticalSection δεν έχει μεγάλη σημασία, καθώς είναι απίθανο να χρειαστεί να κοιτάξετε ποτέ το περιεχόμενό της. Το μόνο που έχετε να κάνετε είναι να περάσετε μια μη αρχικοποιημένη καταχώρηση στην παράμετρο IpCtitical Section και αυτή η καταχώρηση θα συμπληρωθεί αμέσως από τη διαδικασία.

Αφού συμπληρώσετε την καταχώριση στο πρόγραμμα, μπορείτε να δημιουργήσετε μια κρίσιμη ενότητα τοποθετώντας κάποιο τμήμα του κειμένου της μεταξύ των κλήσεων προς τις συναρτήσεις EnterCriticalSection() και LeaveCriticalSection(). Οι διαδικασίες αυτές ορίζονται ως εξής:

διαδικασία EnterCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

διαδικασία LeaveCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Η παράμετρος IpCriticalSection που μεταβιβάζεται σε αυτές τις διαδικασίες δεν είναι τίποτα περισσότερο από μια καταχώρηση που δημιουργήθηκε από τη διαδικασία InitializeCriticalSection().

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

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

Όταν ολοκληρώσετε την εργασία με την εγγραφή TRTLCriticalSection, πρέπει να την απελευθερώσετε καλώντας τη διαδικασία DeleteCriticalSection(), η οποία ορίζεται ως εξής:

διαδικασία DeleteCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Μερικές φορές όταν εργάζεστε με πολλά νήματα ή διεργασίες καθίσταται απαραίτητο συγχρονισμός της εκτέλεσηςδύο ή περισσότερα από αυτά. Ο λόγος για αυτό είναι τις περισσότερες φορές ότι δύο ή περισσότερα νήματα μπορεί να απαιτούν πρόσβαση σε έναν κοινόχρηστο πόρο που Πραγματικάδεν μπορεί να παρασχεθεί σε πολλά νήματα ταυτόχρονα. Ένας κοινόχρηστος πόρος είναι ένας πόρος στον οποίο μπορείτε να έχετε πρόσβαση ταυτόχρονα από πολλαπλές εργασίες που εκτελούνται.

Ο μηχανισμός που διασφαλίζει τη διαδικασία συγχρονισμού ονομάζεται περιορισμός πρόσβασης.Η ανάγκη για αυτό προκύπτει επίσης σε περιπτώσεις όπου ένα νήμα περιμένει ένα συμβάν που δημιουργείται από ένα άλλο νήμα. Φυσικά, πρέπει να υπάρχει κάποιος τρόπος με τον οποίο το πρώτο νήμα θα ανασταλεί πριν συμβεί το συμβάν. Μετά από αυτό, το νήμα θα πρέπει να συνεχίσει την εκτέλεσή του.

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

Τα Windows διαθέτουν ειδικές υπηρεσίες που σας επιτρέπουν να περιορίσετε την πρόσβαση σε κοινόχρηστους πόρους με συγκεκριμένο τρόπο, επειδή χωρίς τη βοήθεια του λειτουργικού συστήματος, μια ξεχωριστή διαδικασία ή νήμα δεν μπορεί να καθορίσει από μόνη της εάν έχει αποκλειστική πρόσβαση σε έναν πόρο. Το λειτουργικό σύστημα Windows περιέχει μια διαδικασία που, κατά τη διάρκεια μιας συνεχούς λειτουργίας, ελέγχει και, εάν είναι δυνατόν, ορίζει τη σημαία πρόσβασης σε πόρους. Στη γλώσσα των προγραμματιστών λειτουργικών συστημάτων, αυτή η λειτουργία ονομάζεται έλεγχος και λειτουργία εγκατάστασης. Οι σημαίες που χρησιμοποιούνται για την παροχή συγχρονισμού και ελέγχου πρόσβασης σε πόρους ονομάζονται σηματοφόροι(σηματοφόρος). Το Win32 API παρέχει υποστήριξη για σηματοφόρους και άλλα αντικείμενα συγχρονισμού. Η βιβλιοθήκη MFC περιλαμβάνει επίσης υποστήριξη για αυτά τα αντικείμενα.

Αντικείμενα συγχρονισμού και κλάσεις mfc

Η διεπαφή Win32 υποστηρίζει τέσσερις τύπους αντικειμένων συγχρονισμού - όλα βασίζονται κατά κάποιο τρόπο στην έννοια του σηματοφόρου.

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

Ο δεύτερος τύπος αντικειμένων συγχρονισμού είναι αποκλειστικός (mutex) σηματοφόρος. Έχει σχεδιαστεί για να περιορίζει πλήρως την πρόσβαση σε έναν πόρο, έτσι ώστε μόνο μία διεργασία ή νήμα να μπορεί να έχει πρόσβαση στον πόρο ανά πάσα στιγμή. Στην πραγματικότητα, πρόκειται για έναν ειδικό τύπο σηματοφόρου.

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

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

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

Στο MFC, ο μηχανισμός συγχρονισμού που παρέχεται από τη διεπαφή Win32 υποστηρίζεται από τις ακόλουθες κλάσεις, οι οποίες προέρχονται από την κλάση CSyncObject:

    Κρίσιμη Ενότητα- υλοποιεί το κρίσιμο τμήμα.

    CEvent- υλοποιεί ένα αντικείμενο συμβάντος

    CMutex- υλοποιεί έναν αποκλειστικό σηματοφόρο.

    CSemaphore- υλοποιεί ένα κλασικό σηματοφόρο.

Εκτός από αυτές τις κατηγορίες, το MFC ορίζει επίσης δύο βοηθητικές τάξεις συγχρονισμού: CSingleLockΚαι CMultiLock. Ελέγχουν την πρόσβαση στο αντικείμενο συγχρονισμού και περιέχουν μεθόδους που χρησιμοποιούνται για την παροχή και την απελευθέρωση τέτοιων αντικειμένων. Τάξη CSingleLockελέγχει την πρόσβαση σε ένα μεμονωμένο αντικείμενο συγχρονισμού και την κλάση CMultiLock- σε πολλά αντικείμενα. Σε αυτό που ακολουθεί θα εξετάσουμε μόνο την τάξη CSingleLock.

Μόλις δημιουργηθεί οποιοδήποτε αντικείμενο συγχρονισμού, η πρόσβαση σε αυτό μπορεί να ελεγχθεί χρησιμοποιώντας την κλάση CSingleLock. Για να γίνει αυτό, πρέπει πρώτα να δημιουργήσετε ένα αντικείμενο τύπου CSingleLockχρησιμοποιώντας τον κατασκευαστή:

CSingleLock(CSyncObject* pObject, BOOL bInitialLock = FALSE);

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

Όταν δημιουργείται ένα αντικείμενο τύπου CSingleLock, η πρόσβαση στο αντικείμενο που δείχνει το pObject μπορεί να ελεγχθεί χρησιμοποιώντας δύο λειτουργίες: ΚλειδαριάΚαι Ξεκλείδωματάξη CSingleLock.

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

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

Όταν εργάζεστε με μια τάξη CSingleLockΗ γενική διαδικασία για τον έλεγχο της πρόσβασης σε έναν πόρο είναι:

    δημιουργήστε ένα αντικείμενο τύπου CSyncObj (για παράδειγμα, ένα σηματοφόρο) που θα χρησιμοποιηθεί για τον έλεγχο της πρόσβασης στον πόρο.

    χρησιμοποιώντας το δημιουργημένο αντικείμενο συγχρονισμού, δημιουργήστε ένα αντικείμενο τύπου CSingleLock.

    Για να αποκτήσετε πρόσβαση σε έναν πόρο, καλέστε τη μέθοδο Lock.

    πρόσβαση σε έναν πόρο·

    Καλέστε τη μέθοδο Ξεκλείδωμα για να απελευθερώσετε τον πόρο.

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

Γειά σου! Σήμερα θα συνεχίσουμε να εξετάζουμε τα χαρακτηριστικά του προγραμματισμού πολλαπλών νημάτων και να μιλάμε για συγχρονισμό νημάτων.

Τι είναι ο «συγχρονισμός»; Εκτός του πεδίου προγραμματισμού, αυτό αναφέρεται σε κάποιο είδος ρύθμισης που επιτρέπει σε δύο συσκευές ή προγράμματα να συνεργάζονται. Για παράδειγμα, ένα smartphone και ένας υπολογιστής μπορούν να συγχρονιστούν με έναν λογαριασμό Google και ένας προσωπικός λογαριασμός σε έναν ιστότοπο μπορεί να συγχρονιστεί με λογαριασμούς σε κοινωνικά δίκτυα για να συνδεθείτε χρησιμοποιώντας τους. Ο συγχρονισμός νημάτων έχει παρόμοια σημασία: ρυθμίζει τον τρόπο αλληλεπίδρασης των νημάτων μεταξύ τους. Σε προηγούμενες διαλέξεις, τα νήματα μας ζούσαν και λειτουργούσαν χωριστά το ένα από το άλλο. Ο ένας μετρούσε κάτι, ο δεύτερος κοιμόταν, ο τρίτος έδειχνε κάτι στην κονσόλα, αλλά δεν αλληλεπιδρούσαν μεταξύ τους. Σε πραγματικά προγράμματα τέτοιες καταστάσεις είναι σπάνιες. Πολλά νήματα μπορούν να λειτουργήσουν ενεργά, για παράδειγμα, με το ίδιο σύνολο δεδομένων και να αλλάξουν κάτι σε αυτό. Αυτό δημιουργεί προβλήματα. Φανταστείτε ότι πολλά νήματα γράφουν κείμενο στην ίδια θέση - για παράδειγμα, ένα αρχείο κειμένου ή την κονσόλα. Αυτό το αρχείο ή η κονσόλα σε αυτήν την περίπτωση γίνεται κοινόχρηστος πόρος. Τα νήματα δεν γνωρίζουν το ένα για την ύπαρξη του άλλου, επομένως απλώς γράφουν όλα όσα μπορούν να διαχειριστούν στο χρόνο που τους εκχωρεί ο προγραμματιστής νημάτων. Σε μια πρόσφατη διάλεξη του μαθήματος, είχαμε ένα παράδειγμα για το τι θα οδηγούσε αυτό, ας το θυμόμαστε: Ο λόγος έγκειται στο γεγονός ότι τα νήματα λειτουργούσαν με έναν κοινόχρηστο πόρο, την κονσόλα, χωρίς να συντονίζουν ενέργειες μεταξύ τους. Εάν ο προγραμματιστής νημάτων έχει διαθέσει χρόνο στο Thread-1, γράφει αμέσως τα πάντα στην κονσόλα. Το τι άλλα θέματα έχουν ήδη καταφέρει να γράψουν ή δεν έχουν προλάβει να γράψουν δεν είναι σημαντικό. Το αποτέλεσμα, όπως βλέπετε, είναι καταστροφικό. Ως εκ τούτου, στον προγραμματισμό πολλαπλών νημάτων εισήχθη μια ειδική έννοια mutex (από τα αγγλικά "mutex", "mutual exclusion" - "mutual exclusion"). Εργασία Mutex- παρέχετε έναν μηχανισμό ώστε μόνο ένα νήμα να έχει πρόσβαση σε ένα αντικείμενο τη δεδομένη στιγμή. Εάν το νήμα-1 έχει αποκτήσει το mutex του αντικειμένου Α, άλλα νήματα δεν θα έχουν πρόσβαση σε αυτό για να αλλάξουν οτιδήποτε σε αυτό. Μέχρι να απελευθερωθεί το mutex του αντικειμένου Α, τα υπόλοιπα νήματα θα αναγκαστούν να περιμένουν. Παράδειγμα πραγματικής ζωής: φανταστείτε ότι εσείς και άλλοι 10 άγνωστοι συμμετέχετε σε μια εκπαίδευση. Πρέπει να εκφράζεις με τη σειρά σου ιδέες και να συζητάς κάτι. Αλλά, επειδή βλέπετε ο ένας τον άλλον για πρώτη φορά, για να μην διακόπτετε συνεχώς ο ένας τον άλλον και να μην γλιστράτε σε μια φασαρία, χρησιμοποιείτε τον κανόνα "μιλώντας μπάλα": μόνο ένα άτομο μπορεί να μιλήσει - αυτός που έχει την μπάλα μέσα τα χέρια του. Έτσι η συζήτηση αποδεικνύεται επαρκής και γόνιμη. Άρα, ένα mutex, στην ουσία, είναι μια τέτοια μπάλα. Εάν το mutex ενός αντικειμένου βρίσκεται στα χέρια ενός νήματος, τα άλλα νήματα δεν θα μπορούν να έχουν πρόσβαση στο αντικείμενο. Δεν χρειάζεται να κάνετε τίποτα για να δημιουργήσετε ένα mutex: είναι ήδη ενσωματωμένο στην κλάση Object, που σημαίνει ότι κάθε αντικείμενο στην Java έχει ένα.

Πώς λειτουργεί ο συγχρονισμένος χειριστής

Ας εξοικειωθούμε με μια νέα λέξη-κλειδί - συγχρονισμένα. Σηματοδοτεί ένα συγκεκριμένο κομμάτι του κώδικά μας. Εάν ένα μπλοκ κώδικα επισημαίνεται με τη συγχρονισμένη λέξη-κλειδί, σημαίνει ότι το μπλοκ μπορεί να εκτελεστεί μόνο από ένα νήμα τη φορά. Ο συγχρονισμός μπορεί να υλοποιηθεί με διαφορετικούς τρόπους. Για παράδειγμα, δημιουργήστε μια ολόκληρη συγχρονισμένη μέθοδο: δημόσια συγχρονισμένη void doSomething() ( //...λογική μεθόδου) Ή γράψτε ένα μπλοκ κώδικα όπου ο συγχρονισμός πραγματοποιείται σε κάποιο αντικείμενο: δημόσια κλάση Main ( ιδιωτικό αντικείμενο obj = νέο αντικείμενο () ; δημόσιο κενό doSomething () ( συγχρονισμένο (obj) ( ) ) Το νόημα είναι απλό. Εάν ένα νήμα εισαγάγει ένα μπλοκ κώδικα που επισημαίνεται με τη λέξη συγχρονισμένη, αποκτά αμέσως το mutex του αντικειμένου και όλα τα άλλα νήματα που προσπαθούν να εισέλθουν στο ίδιο μπλοκ ή μέθοδο αναγκάζονται να περιμένουν έως ότου το προηγούμενο νήμα ολοκληρώσει την εργασία του και απελευθερώσει το οθόνη. Παρεμπιπτόντως! Στις διαλέξεις του μαθήματος είδατε ήδη παραδείγματα συγχρονισμένων , αλλά έμοιαζαν διαφορετικά: δημόσια εναλλαγή κενού () ( συγχρονισμένη (αυτό) ( //...λογική μεθόδου) ) Το θέμα είναι καινούργιο για εσάς, και φυσικά θα υπάρξει σύγχυση με τη σύνταξη στην αρχή. Επομένως, θυμηθείτε αμέσως για να μην μπερδευτείτε αργότερα στις μεθόδους γραφής. Αυτοί οι δύο τρόποι γραφής σημαίνουν το ίδιο πράγμα: δημόσια εναλλαγή κενού () ( συγχρονισμένο (αυτό) ( //...λογική μεθόδου) ) δημόσια συγχρονισμένη εναλλαγή κενού () ( ) ) Στην πρώτη περίπτωση, δημιουργείτε ένα συγχρονισμένο μπλοκ κώδικα αμέσως μόλις εισέλθετε στη μέθοδο. Συγχρονίζεται με αυτό το αντικείμενο, δηλαδή με το τρέχον αντικείμενο. Και στο δεύτερο παράδειγμα βάζετε τη λέξη συγχρονισμένη σε ολόκληρη τη μέθοδο. Δεν υπάρχει πλέον καμία ανάγκη να υποδεικνύεται ρητά οποιοδήποτε αντικείμενο στο οποίο πραγματοποιείται ο συγχρονισμός. Μόλις μια ολόκληρη μέθοδος επισημανθεί με μια λέξη, αυτή η μέθοδος θα συγχρονιστεί αυτόματα για όλα τα αντικείμενα της κλάσης. Ας μην εμβαθύνουμε στη συζήτηση για το ποια μέθοδος είναι καλύτερη. Προς το παρόν, επιλέξτε αυτό που σας αρέσει περισσότερο :) Το κύριο πράγμα είναι να θυμάστε: μπορείτε να δηλώσετε μια μέθοδο συγχρονισμένη μόνο όταν όλη η λογική μέσα σε αυτήν εκτελείται από ένα νήμα ταυτόχρονα. Για παράδειγμα, σε αυτήν την περίπτωση θα ήταν σφάλμα να συγχρονιστεί η μέθοδος doSomething(): public class Main ( private Object obj = new Object () ; public void doSomething () ( //...κάποια λογική διαθέσιμη σε όλα τα νήματασυγχρονισμένος (obj) ( //λογική που είναι διαθέσιμη μόνο σε ένα νήμα τη φορά) ) ) Όπως μπορείτε να δείτε, ένα κομμάτι της μεθόδου περιέχει λογική για την οποία δεν απαιτείται συγχρονισμός. Ο κώδικας σε αυτό μπορεί να εκτελεστεί από πολλά νήματα ταυτόχρονα και όλες οι κρίσιμες θέσεις εκχωρούνται σε ένα ξεχωριστό συγχρονισμένο μπλοκ. Και μια στιγμή. Ας δούμε στο μικροσκόπιο το παράδειγμα ανταλλαγής ονομάτων μας από τη διάλεξη: δημόσια εναλλαγή κενού () ( συγχρονισμένο (αυτό) ( //...λογική μεθόδου } } Δώσε προσοχή: Ο συγχρονισμός πραγματοποιείται χρησιμοποιώντας αυτό. Δηλαδή σε ένα συγκεκριμένο αντικείμενο MyClass. Φανταστείτε ότι έχουμε 2 νήματα (Thread-1 και Thread-2) και μόνο ένα αντικείμενο MyClass myClass . Σε αυτήν την περίπτωση, εάν το Thread-1 καλέσει τη μέθοδο myClass.swap(), το mutex του αντικειμένου θα είναι απασχολημένο και το Thread-2, όταν προσπαθεί να καλέσει το myClass.swap(), θα κρεμάσει περιμένοντας να γίνει ελεύθερο το mutex. Εάν έχουμε 2 νήματα και 2 αντικείμενα MyClass - myClass1 και myClass2 - σε διαφορετικά αντικείμενα, τα νήματα μας μπορούν εύκολα να εκτελέσουν συγχρονισμένες μεθόδους ταυτόχρονα. Το πρώτο νήμα εκτελείται: myClass1. ανταλαγή(); Το δεύτερο κάνει: myClass2. ανταλαγή(); Σε αυτήν την περίπτωση, η συγχρονισμένη λέξη-κλειδί μέσα στη μέθοδο swap() δεν θα επηρεάσει τη λειτουργία του προγράμματος, αφού ο συγχρονισμός εκτελείται σε ένα συγκεκριμένο αντικείμενο. Και στην τελευταία περίπτωση, έχουμε 2 αντικείμενα Επομένως, τα νήματα δεν δημιουργούν προβλήματα μεταξύ τους. Παρά όλα αυτά δύο αντικείμενα έχουν 2 διαφορετικά mutexes και η απόκτησή τους είναι ανεξάρτητη το ένα από το άλλο.

Χαρακτηριστικά συγχρονισμού σε στατικές μεθόδους

Τι να κάνετε εάν χρειάζεται να κάνετε συγχρονισμό στατική μέθοδος? κλάση MyClass ( ιδιωτικό στατικό όνομα συμβολοσειράς1 = "Olya" ; ιδιωτικό στατικό όνομα συμβολοσειράς2 = "Λένα" ; δημόσια στατική συγχρονισμένη εναλλαγή κενού () ( συμβολοσειρά s = όνομα1; όνομα1 = όνομα2; όνομα2 = s; ) ) Δεν είναι σαφές τι θα εκπληρώσει τον ρόλο mutex σε αυτή την περίπτωση. Εξάλλου, έχουμε ήδη αποφασίσει ότι κάθε αντικείμενο έχει ένα mutex. Αλλά το πρόβλημα είναι ότι για να καλέσουμε τη στατική μέθοδο MyClass.swap() δεν χρειαζόμαστε αντικείμενα: η μέθοδος είναι στατική! Λοιπόν, τι ακολουθεί; :/ Στην πραγματικότητα, δεν υπάρχει πρόβλημα με αυτό. Οι δημιουργοί της Java φρόντισαν για όλα :) Εάν η μέθοδος που περιέχει την κρίσιμη λογική "πολυνηματική" είναι στατική, ο συγχρονισμός θα πραγματοποιηθεί ανά τάξη. Για μεγαλύτερη σαφήνεια, ο παραπάνω κώδικας μπορεί να ξαναγραφεί ως εξής: class MyClass ( private static String name1 = "Olya" ; private static String name2 = "Lena" ; public static void swap () ( συγχρονισμένη (MyClass. class ) ( String s = name1 ; name1 = name2; name2 = s; ) ) ) Κατ 'αρχήν, θα μπορούσατε να το σκεφτείτε μόνοι σας: αφού δεν υπάρχουν αντικείμενα, τότε ο μηχανισμός συγχρονισμού πρέπει με κάποιο τρόπο να "ενσωματωθεί" στις ίδιες τις κλάσεις. Έτσι είναι: μπορείτε επίσης να συγχρονίσετε μεταξύ των τάξεων.