Upload
litzelrockbass
View
29
Download
0
Embed Size (px)
Citation preview
Γιώργος Α ΠαπαδόπουλοςΤμήμα ΠληροφορικήςΠανεπιστήμιο Κύπρου
ΕΠΛ222 Λειτουργικά Συστήματα(μετάφραση στα ελληνικά των διαφανειών του βιβλίου Operating Systems Internals and
Design Principles 6E William Stallings)
Ενότητα 4Συντρέχων Προγραμματισμός
Οι διαφάνειες αυτές έχουνσυμπληρωματικό και επεξηγηματικόχαρακτήρα και σε καμία περίπτωση
δεν υποκαθιστούν το βιβλίο
22
Περιεχόμενα
ndashΤαυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια τουαμοιβαίου αποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
33
Ταυτοχρονισμός καιΠαραλληλισμός
bull Με τον όρο ταυτοχρονισμό (concurrency) αναφερόμαστε στην ταυτόχρονη εκτέλεση δύο ήπερισσοτέρων διεργασιών σε ένα σύστημα
bull Αυτό επιτυγχάνεται με τη συνεχή εναλλαγή τωνεκτελούμενων διεργασιών στην KME
bull Σημειωτέον ότι ο ταυτοχρονισμός διαφέρει απότον παραλληλισμό (parallelism) όπου τοσύστημα διαθέτει πολλούς επεξεργαστές οιοποίοι εκτελούν παράλληλα περισσότερες απόμία διεργασίες
44
Σενάριο ταυτοχρονισμού
55
Σενάριο παραλληλισμού
66
Λόγοι ανάγκης για ταυτόχρονηεκτέλεση διεργασιών
bull Πολυπρογραμματισμός (multiprogramming) Ηδιαχείριση πολλαπλών διεργασιών σε ένασύστημα με έναν επεξεργαστή
bull Πολυεπεξεργασία (multiprocessing) Ηδιαχείριση πολλαπλών διεργασιών σε ένασύστημα με πολλούς επεξεργαστές
bull Κατανεμημένη επεξεργασία (distributed processing) Η διαχείριση πολλαπλώνδιεργασιών σε ένα σύστημα με πολλαπλούςΗΥ συνδεδεμένους μεταξύ τους με κάποιοδίκτυο
77
Θέματα σχετιζόμενα με τονταυτοχρονισμό
bull Επικοινωνία μεταξύ των διεργασιώνbull Διαμοιρασμός και ανταγωνιστικότητα στηχρήση των πόρων του συστήματος (όπωςμνήμη αρχεία συσκευές)
bull Συγχρονισμός μεταξύ των διεργασιώνbull Κατανομή του χρόνου της (ή των) ΚΜΕστις διεργασίες
88
Πηγές ταυτοχρονίας
bull Ταυτόχρονη εκτέλεση πολλαπλών εφαρμογώνμε διαμοιρασμό του χρόνου της ΚΜΕ μεταξύτους
bull Ο δομημένος προγραμματισμός επιτρέπει τοσχεδιασμό ενός προγράμματος ως μια ομάδααπό ταυτόχρονα εκτελούμενες διεργασίες
bull Το ίδιο το ΛΣ αποτελείται από μία μεγάληομάδα από ταυτόχρονα εκτελούμενες διεργασίεςήκαι νήματα
99
Συντρέχων προγραμματισμός
bull Δημιουργείται η ανάγκη παροχής στονπρογραμματιστή μηχανισμών για τηνυποστήριξη τεχνικών προγραμματισμούβασισμένων στην έννοια του ταυτοχρονισμού όπως δημιουργία νέων διεργασιών συντονισμόςτης ταυτόχρονης εκτέλεσης ενός αριθμούδιεργασιών διαδιεργασιακή (interprocess) επικοινωνία δηλαδή επικοινωνία μεταξύταυτόχρονα εκτελούμενων διεργασιών κλπ
bull H μορφή αυτή προγραμματισμού αναφέρεται ωςσυντρέχων ή σύνδρομος (concurrent) προγραμματισμός
1010
Συρρουτίνεςbull Ιστορικά ο προπομπός του συντρέχοντος προγραμματισμού είναι ο
μηχανισμός των συρρουτινών (coroutines) που προτάθηκε από τονConway το 1963 O προγραμματισμός με συρρουτίνες ήτανπροέκταση του προγραμματισμού με διαδικασίες (procedures) ενώστην περίπτωση των διαδικασιών μία διαδικασία έπρεπε νααποπερατώσει την εκτέλεσή της πριν ο έλεγχος εκτέλεσης τουπρογράμματος επιστρέψει στη διαδικασία που την κάλεσε στηνπερίπτωση των συρρουτινών ο έλεγχος εκτέλεσης τουπρογράμματος μπορούσε να μεταφερθεί από τη μία συρρουτίναστην άλλη οποιαδήποτε στιγμή επιπλέον η καλούμενη συρρουτίναεπανάρχιζε την εκτέλεσή της από το σημείο που είχε σταματήσει
bull H διαφορά της έννοιας των συρρουτινών από αυτή τουσυντρέχοντος προγραμματισμού είναι ότι μόνο μία συρρουτίνα είναιανά πάσα χρονική στιγμή ενεργοποιημένη και ότι η εναλλαγήενεργοποίησης των συρρουτινών γίνεται στατικά σε συγκεκριμένασημεία ενός προγράμματος
1111
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 1
1212
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 2
1313
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 3
1414
Πρόβλημα συντονισμού αριθμούδιαδικασιών
bull Να διαβαστούν κάρτες των 80 χαρακτήρων και νατυπωθούν σε γραμμές των 125 χαρακτήρων μετά δε τοτέλος κάθε κάρτας να εισαχθεί ένας κενός χαρακτήραςκαι κάθε δύο να αντικατασταθούν με ένα
bull Σειριακή λύση με 3 διαδικασίεςndash Διαδικασία 1η Διάβασε όλες τις κάρτες σε προσωρινή μνήμη
(buffer)ndash Διαδικασία 2η Κάνε τις αντικαταστάσειςndash Διαδικασία 3η Τύπωσε το αποτέλεσμα σε γραμμές των 125 χαρακτήρων
bull Μειονεκτήματα Χάσιμο χρόνου (overhead) λόγω του ότιεκτελούνται πολλές εντολές EE αλλά και μεγάλησπατάλη μνήμης
1515
Επίλυση με συρρουτίνεςchar rs spchar inbuf[80] outbuf[125]
void read() while (1)
READCARD (inbuf)for (int i=0 i lt 80 i++)
rs = inbuf [i]RESUME squash
rs = RESUME squash
void print() while (1)
for (int j = 0 j lt 125 j++) outbuf [j] = spRESUME squash
OUTPUT (outbuf)
void squash() while (1)
if (rs = ) sp = rsRESUME print
else
RESUME readif (rs == )
sp = RESUME print
else
sp = RESUME print sp = rsRESUME print
RESUME read
1616
Προβλήματα που δημιουργεί οταυτοχρονισμός (και ανάλογες
ευθύνες για το ΛΣ)bull Διαχείριση των εκτελούμενων διεργασιώνbull Βέλτιστη κατανομή των διαθέσιμων πόρων αναφορικάμε KME κύρια και περιφερειακή μνήμη και συσκευές EE μεταξύ των εκτελούμενων διεργασιών
bull Προστασία του περιβάλλοντος μίας διεργασίας από μηεπιτρεπτές παρεμβολές από τις άλλες διεργασίες
bull Ανεξαρτησία των αποτελεσμάτων που παράγει μίαδιεργασία από την ταχύτητα εκτέλεσής της σε σχέση μετις άλλες διεργασίες του συστήματος
bull Δυσκολία αποσφαλμάτωσης (debugging) τωνπρογραμμάτων γιατί συνήθως η εκτέλεση τωνδιεργασιών και η εν γένει συμπεριφορά του συστήματοςείναι μη προκαθορισμένη
1717
Αλληλεπίδραση μεταξύ διεργασιώνbull H ταυτόχρονη εκτέλεση περισσοτέρων της μίας διεργασιών οδηγεί συχνά σε
φαινόμενα αλληλεπίδρασης μεταξύ των διεργασιών αυτών (process interaction) που μπορούν να χωρισθούν σε 3 κατηγορίες ανάλογα με τοβαθμό στον οποίο μία διεργασία γνωρίζει την ύπαρξη άλλων διεργασιών καιπως τυχόν εκμεταλλεύεται αυτή την πληροφορία
ndash Οι διεργασίες δεν γνωρίζουν η μία την ύπαρξη της άλλης (πχ οι διεργασίες πουαντιστοιχούν στο κέλυφος ενός χρήστη στο Unix) Σε αυτή την περίπτωση δεντίθεται θέμα υποστήριξης διαδιεργασιακής επικοινωνίας αλλά μπορεί ναδημιουργηθεί πρόβλημα ανταγωνισμού (competition) δηλαδή προσπάθεια απότις εκτελούμενες διεργασίες να δεσμεύσουν τους ίδιους πόρους
ndash Οι διεργασίες γνωρίζουν έμμεσα η μία την ύπαρξη της άλλης (πχ μέσω τηςκοινής χρήσης ενός διαμοιραζόμενου αντικειμένου όπως λχ προσωρινή μνήμη(buffer)) Σε αυτή την περίπτωση δημιουργείται το φαινόμενο της συνεργασίας(cooperation) μέσω προσπέλασης στο κοινής χρήσης αντικείμενο
ndash Οι διεργασίες γνωρίζουν άμεσα η μία την ύπαρξη της άλλης (πχ στη χρήσηδιοχετεύσεων στο Unix) Σε αυτή την περίπτωση οι διεργασίες γνωρίζουν η μίατην άλλη κατrsquo όνομα και έχουν σχεδιασθεί για να εργάζονται μαζί για την επίτευξηενός στόχου Επομένως και εδώ έχουμε το φαινόμενο της συνεργασίας
1818
Ανταγωνισμός μεταξύ διεργασιώνOut = 4
In = 7
P0
gradesxls
essaydoc
progc
7
4
6
5
H διεργασία P0 διαβάζει την τιμή τηςμεταβλητής In που δείχνει στην επόμενηελεύθερη θέση στον κατάλογο των αρχείων πουπεριμένουν να εκτυπωθούν αλλά πρινολοκληρώσει την εργασία της διακόπτεται ηεκτέλεσή της και ξεκινά η εκτέλεση τηςδιεργασίας P1 η οποία αφού διαβάσει και αυτήτην τιμή της In αποθηκεύει στη θέση 7 τοαρχείο προς εκτύπωση και ανεβάζει την τιμήτης μεταβλητής In κατά μία μονάδα Τώραεπαναρχίζει η εκτέλεση της διεργασίας P0 απότο σημείο διακοπής της η οποία κάνονταςχρήση της παλιάς τιμής τής In τοποθετεί τοδικό της αρχείο στη θέση 7 αντί της 8 Αποτέλεσμα είναι ότι το αρχείο της διεργασίαςP1 δεν θα εκτυπωθεί ποτέ Όταν ένα τελικόαποτέλεσμα εξαρτάται από τη σειρά ήκαι τονχρόνο εκτέλεσης δύο ή περισσοτέρωνδιεργασιών τότε δημιουργούνται συνθήκεςανταγωνισμού (race conditions)
P1
1919
Αμοιβαίος αποκλεισμός
bull Για την αποφυγή συνθηκών ανταγωνισμού επιβάλλεται οαμοιβαίος αποκλεισμός (mutual exclusion) δηλαδή οαποκλεισμός μίας διεργασίας από κάποια ενέργεια πουταυτόχρονα επιτελεί κάποια άλλη διεργασία Αυτό οδηγείστον καθορισμό κάποιων μερών στον κώδικα μίαςδιεργασίας στα οποία η διεργασία αυτή προσπαθεί ναπροσπελάσει κοινά μεταξύ των διεργασιών αντικείμενα(πχ διαμοιραζόμενες περιοχές μνήμης αρχεία κλπ) Αυτά τα μέρη λέγονται κρίσιμα τμήματαπεριοχές (critical sectionsregions) και για τη σωστή εκτέλεσησυντρέχουσων διεργασιών πρέπει να διασφαλιστεί ότιποτέ δύο ή περισσότερες διεργασίες δεν θα βρίσκονταιταυτόχρονα στο ίδιο κρίσιμο τμήμα
2020
Προβλήματα σχετιζόμενα με τοναμοιβαίο αποκλεισμό
bull Αδιέξοδο (deadlock) όταν λχ δύο διεργασίεςχρειάζονται δύο πόρους και κάθε μία από τιςδιεργασίες έχει δεσμεύσει ένα από τους πόρουςκαι έχοντας αρχίσει οι διεργασίες να εκτελούν τοκρίσιμο τμήμα του κώδικά τους για τον δεύτεροπόρο ανακαλύπτουν ότι είναι δεσμευμένος απότην άλλη διεργασία
bull Παρατεταμένη στέρησης (starvation) όπουκάποια διεργασία δεν προλαβαίνει να δεσμεύσεικάποιον πόρο λόγω της πιο γρήγορηςδέσμευσής του από άλλες διεργασίες
2121
Έμμεση συνεργασία μεταξύδιεργασιών
bull Μέσω κοινής χρήσης κάποιων αντικειμένων (μεταβλητών αρχείων βάσεων δεδομένων κλπ) Τα προβλήματα του αμοιβαίουαποκλεισμού του αδιέξοδου και της παρατεταμένης στέρησηςυπάρχουν και εδώ Όμως τα κοινά αντικείμενα μπορούν ναπροσπελασθούν για διάβασμα ή για γράψιμο και μόνο στη δεύτερηπερίπτωση χρειάζεται αμοιβαίος αποκλεισμός Ένα άλλο πρόβλημαπου παρατηρείται εδώ και έχει να κάνει με την ενημέρωση κοινώνδεδομένων είναι αυτό της συνέπειας των δεδομένων (data coherence) όπως φαίνεται στο ακόλουθο παράδειγμαP0 a = a+1 a = a+1 (P0)
b = b+1 b = 2b (P1)P1 b = 2b b = b+1 (P0)
a = 2a a = 2a (P1)όπου το ζητούμενο είναι μετά το τέλος εκτέλεσης των εντολών ναισχύει η σχέση a = b Mε την εκτέλεση στα αριστερά αυτόεπιτυγχάνεται αλλά με την εκτέλεση στα δεξιά δεν επιτυγχάνεταιπαρόλο που οι δύο διεργασίες έχουν σεβασθεί τη συνθήκηαμοιβαίου αποκλεισμού για τις δύο μεταβλητές ξεχωριστά Αυτό πουχρειάζεται είναι και οι τέσσερεις εντολές να θεωρηθούν κρίσιμοτμήμα
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
44
Σενάριο ταυτοχρονισμού
55
Σενάριο παραλληλισμού
66
Λόγοι ανάγκης για ταυτόχρονηεκτέλεση διεργασιών
bull Πολυπρογραμματισμός (multiprogramming) Ηδιαχείριση πολλαπλών διεργασιών σε ένασύστημα με έναν επεξεργαστή
bull Πολυεπεξεργασία (multiprocessing) Ηδιαχείριση πολλαπλών διεργασιών σε ένασύστημα με πολλούς επεξεργαστές
bull Κατανεμημένη επεξεργασία (distributed processing) Η διαχείριση πολλαπλώνδιεργασιών σε ένα σύστημα με πολλαπλούςΗΥ συνδεδεμένους μεταξύ τους με κάποιοδίκτυο
77
Θέματα σχετιζόμενα με τονταυτοχρονισμό
bull Επικοινωνία μεταξύ των διεργασιώνbull Διαμοιρασμός και ανταγωνιστικότητα στηχρήση των πόρων του συστήματος (όπωςμνήμη αρχεία συσκευές)
bull Συγχρονισμός μεταξύ των διεργασιώνbull Κατανομή του χρόνου της (ή των) ΚΜΕστις διεργασίες
88
Πηγές ταυτοχρονίας
bull Ταυτόχρονη εκτέλεση πολλαπλών εφαρμογώνμε διαμοιρασμό του χρόνου της ΚΜΕ μεταξύτους
bull Ο δομημένος προγραμματισμός επιτρέπει τοσχεδιασμό ενός προγράμματος ως μια ομάδααπό ταυτόχρονα εκτελούμενες διεργασίες
bull Το ίδιο το ΛΣ αποτελείται από μία μεγάληομάδα από ταυτόχρονα εκτελούμενες διεργασίεςήκαι νήματα
99
Συντρέχων προγραμματισμός
bull Δημιουργείται η ανάγκη παροχής στονπρογραμματιστή μηχανισμών για τηνυποστήριξη τεχνικών προγραμματισμούβασισμένων στην έννοια του ταυτοχρονισμού όπως δημιουργία νέων διεργασιών συντονισμόςτης ταυτόχρονης εκτέλεσης ενός αριθμούδιεργασιών διαδιεργασιακή (interprocess) επικοινωνία δηλαδή επικοινωνία μεταξύταυτόχρονα εκτελούμενων διεργασιών κλπ
bull H μορφή αυτή προγραμματισμού αναφέρεται ωςσυντρέχων ή σύνδρομος (concurrent) προγραμματισμός
1010
Συρρουτίνεςbull Ιστορικά ο προπομπός του συντρέχοντος προγραμματισμού είναι ο
μηχανισμός των συρρουτινών (coroutines) που προτάθηκε από τονConway το 1963 O προγραμματισμός με συρρουτίνες ήτανπροέκταση του προγραμματισμού με διαδικασίες (procedures) ενώστην περίπτωση των διαδικασιών μία διαδικασία έπρεπε νααποπερατώσει την εκτέλεσή της πριν ο έλεγχος εκτέλεσης τουπρογράμματος επιστρέψει στη διαδικασία που την κάλεσε στηνπερίπτωση των συρρουτινών ο έλεγχος εκτέλεσης τουπρογράμματος μπορούσε να μεταφερθεί από τη μία συρρουτίναστην άλλη οποιαδήποτε στιγμή επιπλέον η καλούμενη συρρουτίναεπανάρχιζε την εκτέλεσή της από το σημείο που είχε σταματήσει
bull H διαφορά της έννοιας των συρρουτινών από αυτή τουσυντρέχοντος προγραμματισμού είναι ότι μόνο μία συρρουτίνα είναιανά πάσα χρονική στιγμή ενεργοποιημένη και ότι η εναλλαγήενεργοποίησης των συρρουτινών γίνεται στατικά σε συγκεκριμένασημεία ενός προγράμματος
1111
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 1
1212
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 2
1313
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 3
1414
Πρόβλημα συντονισμού αριθμούδιαδικασιών
bull Να διαβαστούν κάρτες των 80 χαρακτήρων και νατυπωθούν σε γραμμές των 125 χαρακτήρων μετά δε τοτέλος κάθε κάρτας να εισαχθεί ένας κενός χαρακτήραςκαι κάθε δύο να αντικατασταθούν με ένα
bull Σειριακή λύση με 3 διαδικασίεςndash Διαδικασία 1η Διάβασε όλες τις κάρτες σε προσωρινή μνήμη
(buffer)ndash Διαδικασία 2η Κάνε τις αντικαταστάσειςndash Διαδικασία 3η Τύπωσε το αποτέλεσμα σε γραμμές των 125 χαρακτήρων
bull Μειονεκτήματα Χάσιμο χρόνου (overhead) λόγω του ότιεκτελούνται πολλές εντολές EE αλλά και μεγάλησπατάλη μνήμης
1515
Επίλυση με συρρουτίνεςchar rs spchar inbuf[80] outbuf[125]
void read() while (1)
READCARD (inbuf)for (int i=0 i lt 80 i++)
rs = inbuf [i]RESUME squash
rs = RESUME squash
void print() while (1)
for (int j = 0 j lt 125 j++) outbuf [j] = spRESUME squash
OUTPUT (outbuf)
void squash() while (1)
if (rs = ) sp = rsRESUME print
else
RESUME readif (rs == )
sp = RESUME print
else
sp = RESUME print sp = rsRESUME print
RESUME read
1616
Προβλήματα που δημιουργεί οταυτοχρονισμός (και ανάλογες
ευθύνες για το ΛΣ)bull Διαχείριση των εκτελούμενων διεργασιώνbull Βέλτιστη κατανομή των διαθέσιμων πόρων αναφορικάμε KME κύρια και περιφερειακή μνήμη και συσκευές EE μεταξύ των εκτελούμενων διεργασιών
bull Προστασία του περιβάλλοντος μίας διεργασίας από μηεπιτρεπτές παρεμβολές από τις άλλες διεργασίες
bull Ανεξαρτησία των αποτελεσμάτων που παράγει μίαδιεργασία από την ταχύτητα εκτέλεσής της σε σχέση μετις άλλες διεργασίες του συστήματος
bull Δυσκολία αποσφαλμάτωσης (debugging) τωνπρογραμμάτων γιατί συνήθως η εκτέλεση τωνδιεργασιών και η εν γένει συμπεριφορά του συστήματοςείναι μη προκαθορισμένη
1717
Αλληλεπίδραση μεταξύ διεργασιώνbull H ταυτόχρονη εκτέλεση περισσοτέρων της μίας διεργασιών οδηγεί συχνά σε
φαινόμενα αλληλεπίδρασης μεταξύ των διεργασιών αυτών (process interaction) που μπορούν να χωρισθούν σε 3 κατηγορίες ανάλογα με τοβαθμό στον οποίο μία διεργασία γνωρίζει την ύπαρξη άλλων διεργασιών καιπως τυχόν εκμεταλλεύεται αυτή την πληροφορία
ndash Οι διεργασίες δεν γνωρίζουν η μία την ύπαρξη της άλλης (πχ οι διεργασίες πουαντιστοιχούν στο κέλυφος ενός χρήστη στο Unix) Σε αυτή την περίπτωση δεντίθεται θέμα υποστήριξης διαδιεργασιακής επικοινωνίας αλλά μπορεί ναδημιουργηθεί πρόβλημα ανταγωνισμού (competition) δηλαδή προσπάθεια απότις εκτελούμενες διεργασίες να δεσμεύσουν τους ίδιους πόρους
ndash Οι διεργασίες γνωρίζουν έμμεσα η μία την ύπαρξη της άλλης (πχ μέσω τηςκοινής χρήσης ενός διαμοιραζόμενου αντικειμένου όπως λχ προσωρινή μνήμη(buffer)) Σε αυτή την περίπτωση δημιουργείται το φαινόμενο της συνεργασίας(cooperation) μέσω προσπέλασης στο κοινής χρήσης αντικείμενο
ndash Οι διεργασίες γνωρίζουν άμεσα η μία την ύπαρξη της άλλης (πχ στη χρήσηδιοχετεύσεων στο Unix) Σε αυτή την περίπτωση οι διεργασίες γνωρίζουν η μίατην άλλη κατrsquo όνομα και έχουν σχεδιασθεί για να εργάζονται μαζί για την επίτευξηενός στόχου Επομένως και εδώ έχουμε το φαινόμενο της συνεργασίας
1818
Ανταγωνισμός μεταξύ διεργασιώνOut = 4
In = 7
P0
gradesxls
essaydoc
progc
7
4
6
5
H διεργασία P0 διαβάζει την τιμή τηςμεταβλητής In που δείχνει στην επόμενηελεύθερη θέση στον κατάλογο των αρχείων πουπεριμένουν να εκτυπωθούν αλλά πρινολοκληρώσει την εργασία της διακόπτεται ηεκτέλεσή της και ξεκινά η εκτέλεση τηςδιεργασίας P1 η οποία αφού διαβάσει και αυτήτην τιμή της In αποθηκεύει στη θέση 7 τοαρχείο προς εκτύπωση και ανεβάζει την τιμήτης μεταβλητής In κατά μία μονάδα Τώραεπαναρχίζει η εκτέλεση της διεργασίας P0 απότο σημείο διακοπής της η οποία κάνονταςχρήση της παλιάς τιμής τής In τοποθετεί τοδικό της αρχείο στη θέση 7 αντί της 8 Αποτέλεσμα είναι ότι το αρχείο της διεργασίαςP1 δεν θα εκτυπωθεί ποτέ Όταν ένα τελικόαποτέλεσμα εξαρτάται από τη σειρά ήκαι τονχρόνο εκτέλεσης δύο ή περισσοτέρωνδιεργασιών τότε δημιουργούνται συνθήκεςανταγωνισμού (race conditions)
P1
1919
Αμοιβαίος αποκλεισμός
bull Για την αποφυγή συνθηκών ανταγωνισμού επιβάλλεται οαμοιβαίος αποκλεισμός (mutual exclusion) δηλαδή οαποκλεισμός μίας διεργασίας από κάποια ενέργεια πουταυτόχρονα επιτελεί κάποια άλλη διεργασία Αυτό οδηγείστον καθορισμό κάποιων μερών στον κώδικα μίαςδιεργασίας στα οποία η διεργασία αυτή προσπαθεί ναπροσπελάσει κοινά μεταξύ των διεργασιών αντικείμενα(πχ διαμοιραζόμενες περιοχές μνήμης αρχεία κλπ) Αυτά τα μέρη λέγονται κρίσιμα τμήματαπεριοχές (critical sectionsregions) και για τη σωστή εκτέλεσησυντρέχουσων διεργασιών πρέπει να διασφαλιστεί ότιποτέ δύο ή περισσότερες διεργασίες δεν θα βρίσκονταιταυτόχρονα στο ίδιο κρίσιμο τμήμα
2020
Προβλήματα σχετιζόμενα με τοναμοιβαίο αποκλεισμό
bull Αδιέξοδο (deadlock) όταν λχ δύο διεργασίεςχρειάζονται δύο πόρους και κάθε μία από τιςδιεργασίες έχει δεσμεύσει ένα από τους πόρουςκαι έχοντας αρχίσει οι διεργασίες να εκτελούν τοκρίσιμο τμήμα του κώδικά τους για τον δεύτεροπόρο ανακαλύπτουν ότι είναι δεσμευμένος απότην άλλη διεργασία
bull Παρατεταμένη στέρησης (starvation) όπουκάποια διεργασία δεν προλαβαίνει να δεσμεύσεικάποιον πόρο λόγω της πιο γρήγορηςδέσμευσής του από άλλες διεργασίες
2121
Έμμεση συνεργασία μεταξύδιεργασιών
bull Μέσω κοινής χρήσης κάποιων αντικειμένων (μεταβλητών αρχείων βάσεων δεδομένων κλπ) Τα προβλήματα του αμοιβαίουαποκλεισμού του αδιέξοδου και της παρατεταμένης στέρησηςυπάρχουν και εδώ Όμως τα κοινά αντικείμενα μπορούν ναπροσπελασθούν για διάβασμα ή για γράψιμο και μόνο στη δεύτερηπερίπτωση χρειάζεται αμοιβαίος αποκλεισμός Ένα άλλο πρόβλημαπου παρατηρείται εδώ και έχει να κάνει με την ενημέρωση κοινώνδεδομένων είναι αυτό της συνέπειας των δεδομένων (data coherence) όπως φαίνεται στο ακόλουθο παράδειγμαP0 a = a+1 a = a+1 (P0)
b = b+1 b = 2b (P1)P1 b = 2b b = b+1 (P0)
a = 2a a = 2a (P1)όπου το ζητούμενο είναι μετά το τέλος εκτέλεσης των εντολών ναισχύει η σχέση a = b Mε την εκτέλεση στα αριστερά αυτόεπιτυγχάνεται αλλά με την εκτέλεση στα δεξιά δεν επιτυγχάνεταιπαρόλο που οι δύο διεργασίες έχουν σεβασθεί τη συνθήκηαμοιβαίου αποκλεισμού για τις δύο μεταβλητές ξεχωριστά Αυτό πουχρειάζεται είναι και οι τέσσερεις εντολές να θεωρηθούν κρίσιμοτμήμα
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
77
Θέματα σχετιζόμενα με τονταυτοχρονισμό
bull Επικοινωνία μεταξύ των διεργασιώνbull Διαμοιρασμός και ανταγωνιστικότητα στηχρήση των πόρων του συστήματος (όπωςμνήμη αρχεία συσκευές)
bull Συγχρονισμός μεταξύ των διεργασιώνbull Κατανομή του χρόνου της (ή των) ΚΜΕστις διεργασίες
88
Πηγές ταυτοχρονίας
bull Ταυτόχρονη εκτέλεση πολλαπλών εφαρμογώνμε διαμοιρασμό του χρόνου της ΚΜΕ μεταξύτους
bull Ο δομημένος προγραμματισμός επιτρέπει τοσχεδιασμό ενός προγράμματος ως μια ομάδααπό ταυτόχρονα εκτελούμενες διεργασίες
bull Το ίδιο το ΛΣ αποτελείται από μία μεγάληομάδα από ταυτόχρονα εκτελούμενες διεργασίεςήκαι νήματα
99
Συντρέχων προγραμματισμός
bull Δημιουργείται η ανάγκη παροχής στονπρογραμματιστή μηχανισμών για τηνυποστήριξη τεχνικών προγραμματισμούβασισμένων στην έννοια του ταυτοχρονισμού όπως δημιουργία νέων διεργασιών συντονισμόςτης ταυτόχρονης εκτέλεσης ενός αριθμούδιεργασιών διαδιεργασιακή (interprocess) επικοινωνία δηλαδή επικοινωνία μεταξύταυτόχρονα εκτελούμενων διεργασιών κλπ
bull H μορφή αυτή προγραμματισμού αναφέρεται ωςσυντρέχων ή σύνδρομος (concurrent) προγραμματισμός
1010
Συρρουτίνεςbull Ιστορικά ο προπομπός του συντρέχοντος προγραμματισμού είναι ο
μηχανισμός των συρρουτινών (coroutines) που προτάθηκε από τονConway το 1963 O προγραμματισμός με συρρουτίνες ήτανπροέκταση του προγραμματισμού με διαδικασίες (procedures) ενώστην περίπτωση των διαδικασιών μία διαδικασία έπρεπε νααποπερατώσει την εκτέλεσή της πριν ο έλεγχος εκτέλεσης τουπρογράμματος επιστρέψει στη διαδικασία που την κάλεσε στηνπερίπτωση των συρρουτινών ο έλεγχος εκτέλεσης τουπρογράμματος μπορούσε να μεταφερθεί από τη μία συρρουτίναστην άλλη οποιαδήποτε στιγμή επιπλέον η καλούμενη συρρουτίναεπανάρχιζε την εκτέλεσή της από το σημείο που είχε σταματήσει
bull H διαφορά της έννοιας των συρρουτινών από αυτή τουσυντρέχοντος προγραμματισμού είναι ότι μόνο μία συρρουτίνα είναιανά πάσα χρονική στιγμή ενεργοποιημένη και ότι η εναλλαγήενεργοποίησης των συρρουτινών γίνεται στατικά σε συγκεκριμένασημεία ενός προγράμματος
1111
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 1
1212
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 2
1313
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 3
1414
Πρόβλημα συντονισμού αριθμούδιαδικασιών
bull Να διαβαστούν κάρτες των 80 χαρακτήρων και νατυπωθούν σε γραμμές των 125 χαρακτήρων μετά δε τοτέλος κάθε κάρτας να εισαχθεί ένας κενός χαρακτήραςκαι κάθε δύο να αντικατασταθούν με ένα
bull Σειριακή λύση με 3 διαδικασίεςndash Διαδικασία 1η Διάβασε όλες τις κάρτες σε προσωρινή μνήμη
(buffer)ndash Διαδικασία 2η Κάνε τις αντικαταστάσειςndash Διαδικασία 3η Τύπωσε το αποτέλεσμα σε γραμμές των 125 χαρακτήρων
bull Μειονεκτήματα Χάσιμο χρόνου (overhead) λόγω του ότιεκτελούνται πολλές εντολές EE αλλά και μεγάλησπατάλη μνήμης
1515
Επίλυση με συρρουτίνεςchar rs spchar inbuf[80] outbuf[125]
void read() while (1)
READCARD (inbuf)for (int i=0 i lt 80 i++)
rs = inbuf [i]RESUME squash
rs = RESUME squash
void print() while (1)
for (int j = 0 j lt 125 j++) outbuf [j] = spRESUME squash
OUTPUT (outbuf)
void squash() while (1)
if (rs = ) sp = rsRESUME print
else
RESUME readif (rs == )
sp = RESUME print
else
sp = RESUME print sp = rsRESUME print
RESUME read
1616
Προβλήματα που δημιουργεί οταυτοχρονισμός (και ανάλογες
ευθύνες για το ΛΣ)bull Διαχείριση των εκτελούμενων διεργασιώνbull Βέλτιστη κατανομή των διαθέσιμων πόρων αναφορικάμε KME κύρια και περιφερειακή μνήμη και συσκευές EE μεταξύ των εκτελούμενων διεργασιών
bull Προστασία του περιβάλλοντος μίας διεργασίας από μηεπιτρεπτές παρεμβολές από τις άλλες διεργασίες
bull Ανεξαρτησία των αποτελεσμάτων που παράγει μίαδιεργασία από την ταχύτητα εκτέλεσής της σε σχέση μετις άλλες διεργασίες του συστήματος
bull Δυσκολία αποσφαλμάτωσης (debugging) τωνπρογραμμάτων γιατί συνήθως η εκτέλεση τωνδιεργασιών και η εν γένει συμπεριφορά του συστήματοςείναι μη προκαθορισμένη
1717
Αλληλεπίδραση μεταξύ διεργασιώνbull H ταυτόχρονη εκτέλεση περισσοτέρων της μίας διεργασιών οδηγεί συχνά σε
φαινόμενα αλληλεπίδρασης μεταξύ των διεργασιών αυτών (process interaction) που μπορούν να χωρισθούν σε 3 κατηγορίες ανάλογα με τοβαθμό στον οποίο μία διεργασία γνωρίζει την ύπαρξη άλλων διεργασιών καιπως τυχόν εκμεταλλεύεται αυτή την πληροφορία
ndash Οι διεργασίες δεν γνωρίζουν η μία την ύπαρξη της άλλης (πχ οι διεργασίες πουαντιστοιχούν στο κέλυφος ενός χρήστη στο Unix) Σε αυτή την περίπτωση δεντίθεται θέμα υποστήριξης διαδιεργασιακής επικοινωνίας αλλά μπορεί ναδημιουργηθεί πρόβλημα ανταγωνισμού (competition) δηλαδή προσπάθεια απότις εκτελούμενες διεργασίες να δεσμεύσουν τους ίδιους πόρους
ndash Οι διεργασίες γνωρίζουν έμμεσα η μία την ύπαρξη της άλλης (πχ μέσω τηςκοινής χρήσης ενός διαμοιραζόμενου αντικειμένου όπως λχ προσωρινή μνήμη(buffer)) Σε αυτή την περίπτωση δημιουργείται το φαινόμενο της συνεργασίας(cooperation) μέσω προσπέλασης στο κοινής χρήσης αντικείμενο
ndash Οι διεργασίες γνωρίζουν άμεσα η μία την ύπαρξη της άλλης (πχ στη χρήσηδιοχετεύσεων στο Unix) Σε αυτή την περίπτωση οι διεργασίες γνωρίζουν η μίατην άλλη κατrsquo όνομα και έχουν σχεδιασθεί για να εργάζονται μαζί για την επίτευξηενός στόχου Επομένως και εδώ έχουμε το φαινόμενο της συνεργασίας
1818
Ανταγωνισμός μεταξύ διεργασιώνOut = 4
In = 7
P0
gradesxls
essaydoc
progc
7
4
6
5
H διεργασία P0 διαβάζει την τιμή τηςμεταβλητής In που δείχνει στην επόμενηελεύθερη θέση στον κατάλογο των αρχείων πουπεριμένουν να εκτυπωθούν αλλά πρινολοκληρώσει την εργασία της διακόπτεται ηεκτέλεσή της και ξεκινά η εκτέλεση τηςδιεργασίας P1 η οποία αφού διαβάσει και αυτήτην τιμή της In αποθηκεύει στη θέση 7 τοαρχείο προς εκτύπωση και ανεβάζει την τιμήτης μεταβλητής In κατά μία μονάδα Τώραεπαναρχίζει η εκτέλεση της διεργασίας P0 απότο σημείο διακοπής της η οποία κάνονταςχρήση της παλιάς τιμής τής In τοποθετεί τοδικό της αρχείο στη θέση 7 αντί της 8 Αποτέλεσμα είναι ότι το αρχείο της διεργασίαςP1 δεν θα εκτυπωθεί ποτέ Όταν ένα τελικόαποτέλεσμα εξαρτάται από τη σειρά ήκαι τονχρόνο εκτέλεσης δύο ή περισσοτέρωνδιεργασιών τότε δημιουργούνται συνθήκεςανταγωνισμού (race conditions)
P1
1919
Αμοιβαίος αποκλεισμός
bull Για την αποφυγή συνθηκών ανταγωνισμού επιβάλλεται οαμοιβαίος αποκλεισμός (mutual exclusion) δηλαδή οαποκλεισμός μίας διεργασίας από κάποια ενέργεια πουταυτόχρονα επιτελεί κάποια άλλη διεργασία Αυτό οδηγείστον καθορισμό κάποιων μερών στον κώδικα μίαςδιεργασίας στα οποία η διεργασία αυτή προσπαθεί ναπροσπελάσει κοινά μεταξύ των διεργασιών αντικείμενα(πχ διαμοιραζόμενες περιοχές μνήμης αρχεία κλπ) Αυτά τα μέρη λέγονται κρίσιμα τμήματαπεριοχές (critical sectionsregions) και για τη σωστή εκτέλεσησυντρέχουσων διεργασιών πρέπει να διασφαλιστεί ότιποτέ δύο ή περισσότερες διεργασίες δεν θα βρίσκονταιταυτόχρονα στο ίδιο κρίσιμο τμήμα
2020
Προβλήματα σχετιζόμενα με τοναμοιβαίο αποκλεισμό
bull Αδιέξοδο (deadlock) όταν λχ δύο διεργασίεςχρειάζονται δύο πόρους και κάθε μία από τιςδιεργασίες έχει δεσμεύσει ένα από τους πόρουςκαι έχοντας αρχίσει οι διεργασίες να εκτελούν τοκρίσιμο τμήμα του κώδικά τους για τον δεύτεροπόρο ανακαλύπτουν ότι είναι δεσμευμένος απότην άλλη διεργασία
bull Παρατεταμένη στέρησης (starvation) όπουκάποια διεργασία δεν προλαβαίνει να δεσμεύσεικάποιον πόρο λόγω της πιο γρήγορηςδέσμευσής του από άλλες διεργασίες
2121
Έμμεση συνεργασία μεταξύδιεργασιών
bull Μέσω κοινής χρήσης κάποιων αντικειμένων (μεταβλητών αρχείων βάσεων δεδομένων κλπ) Τα προβλήματα του αμοιβαίουαποκλεισμού του αδιέξοδου και της παρατεταμένης στέρησηςυπάρχουν και εδώ Όμως τα κοινά αντικείμενα μπορούν ναπροσπελασθούν για διάβασμα ή για γράψιμο και μόνο στη δεύτερηπερίπτωση χρειάζεται αμοιβαίος αποκλεισμός Ένα άλλο πρόβλημαπου παρατηρείται εδώ και έχει να κάνει με την ενημέρωση κοινώνδεδομένων είναι αυτό της συνέπειας των δεδομένων (data coherence) όπως φαίνεται στο ακόλουθο παράδειγμαP0 a = a+1 a = a+1 (P0)
b = b+1 b = 2b (P1)P1 b = 2b b = b+1 (P0)
a = 2a a = 2a (P1)όπου το ζητούμενο είναι μετά το τέλος εκτέλεσης των εντολών ναισχύει η σχέση a = b Mε την εκτέλεση στα αριστερά αυτόεπιτυγχάνεται αλλά με την εκτέλεση στα δεξιά δεν επιτυγχάνεταιπαρόλο που οι δύο διεργασίες έχουν σεβασθεί τη συνθήκηαμοιβαίου αποκλεισμού για τις δύο μεταβλητές ξεχωριστά Αυτό πουχρειάζεται είναι και οι τέσσερεις εντολές να θεωρηθούν κρίσιμοτμήμα
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
1010
Συρρουτίνεςbull Ιστορικά ο προπομπός του συντρέχοντος προγραμματισμού είναι ο
μηχανισμός των συρρουτινών (coroutines) που προτάθηκε από τονConway το 1963 O προγραμματισμός με συρρουτίνες ήτανπροέκταση του προγραμματισμού με διαδικασίες (procedures) ενώστην περίπτωση των διαδικασιών μία διαδικασία έπρεπε νααποπερατώσει την εκτέλεσή της πριν ο έλεγχος εκτέλεσης τουπρογράμματος επιστρέψει στη διαδικασία που την κάλεσε στηνπερίπτωση των συρρουτινών ο έλεγχος εκτέλεσης τουπρογράμματος μπορούσε να μεταφερθεί από τη μία συρρουτίναστην άλλη οποιαδήποτε στιγμή επιπλέον η καλούμενη συρρουτίναεπανάρχιζε την εκτέλεσή της από το σημείο που είχε σταματήσει
bull H διαφορά της έννοιας των συρρουτινών από αυτή τουσυντρέχοντος προγραμματισμού είναι ότι μόνο μία συρρουτίνα είναιανά πάσα χρονική στιγμή ενεργοποιημένη και ότι η εναλλαγήενεργοποίησης των συρρουτινών γίνεται στατικά σε συγκεκριμένασημεία ενός προγράμματος
1111
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 1
1212
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 2
1313
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 3
1414
Πρόβλημα συντονισμού αριθμούδιαδικασιών
bull Να διαβαστούν κάρτες των 80 χαρακτήρων και νατυπωθούν σε γραμμές των 125 χαρακτήρων μετά δε τοτέλος κάθε κάρτας να εισαχθεί ένας κενός χαρακτήραςκαι κάθε δύο να αντικατασταθούν με ένα
bull Σειριακή λύση με 3 διαδικασίεςndash Διαδικασία 1η Διάβασε όλες τις κάρτες σε προσωρινή μνήμη
(buffer)ndash Διαδικασία 2η Κάνε τις αντικαταστάσειςndash Διαδικασία 3η Τύπωσε το αποτέλεσμα σε γραμμές των 125 χαρακτήρων
bull Μειονεκτήματα Χάσιμο χρόνου (overhead) λόγω του ότιεκτελούνται πολλές εντολές EE αλλά και μεγάλησπατάλη μνήμης
1515
Επίλυση με συρρουτίνεςchar rs spchar inbuf[80] outbuf[125]
void read() while (1)
READCARD (inbuf)for (int i=0 i lt 80 i++)
rs = inbuf [i]RESUME squash
rs = RESUME squash
void print() while (1)
for (int j = 0 j lt 125 j++) outbuf [j] = spRESUME squash
OUTPUT (outbuf)
void squash() while (1)
if (rs = ) sp = rsRESUME print
else
RESUME readif (rs == )
sp = RESUME print
else
sp = RESUME print sp = rsRESUME print
RESUME read
1616
Προβλήματα που δημιουργεί οταυτοχρονισμός (και ανάλογες
ευθύνες για το ΛΣ)bull Διαχείριση των εκτελούμενων διεργασιώνbull Βέλτιστη κατανομή των διαθέσιμων πόρων αναφορικάμε KME κύρια και περιφερειακή μνήμη και συσκευές EE μεταξύ των εκτελούμενων διεργασιών
bull Προστασία του περιβάλλοντος μίας διεργασίας από μηεπιτρεπτές παρεμβολές από τις άλλες διεργασίες
bull Ανεξαρτησία των αποτελεσμάτων που παράγει μίαδιεργασία από την ταχύτητα εκτέλεσής της σε σχέση μετις άλλες διεργασίες του συστήματος
bull Δυσκολία αποσφαλμάτωσης (debugging) τωνπρογραμμάτων γιατί συνήθως η εκτέλεση τωνδιεργασιών και η εν γένει συμπεριφορά του συστήματοςείναι μη προκαθορισμένη
1717
Αλληλεπίδραση μεταξύ διεργασιώνbull H ταυτόχρονη εκτέλεση περισσοτέρων της μίας διεργασιών οδηγεί συχνά σε
φαινόμενα αλληλεπίδρασης μεταξύ των διεργασιών αυτών (process interaction) που μπορούν να χωρισθούν σε 3 κατηγορίες ανάλογα με τοβαθμό στον οποίο μία διεργασία γνωρίζει την ύπαρξη άλλων διεργασιών καιπως τυχόν εκμεταλλεύεται αυτή την πληροφορία
ndash Οι διεργασίες δεν γνωρίζουν η μία την ύπαρξη της άλλης (πχ οι διεργασίες πουαντιστοιχούν στο κέλυφος ενός χρήστη στο Unix) Σε αυτή την περίπτωση δεντίθεται θέμα υποστήριξης διαδιεργασιακής επικοινωνίας αλλά μπορεί ναδημιουργηθεί πρόβλημα ανταγωνισμού (competition) δηλαδή προσπάθεια απότις εκτελούμενες διεργασίες να δεσμεύσουν τους ίδιους πόρους
ndash Οι διεργασίες γνωρίζουν έμμεσα η μία την ύπαρξη της άλλης (πχ μέσω τηςκοινής χρήσης ενός διαμοιραζόμενου αντικειμένου όπως λχ προσωρινή μνήμη(buffer)) Σε αυτή την περίπτωση δημιουργείται το φαινόμενο της συνεργασίας(cooperation) μέσω προσπέλασης στο κοινής χρήσης αντικείμενο
ndash Οι διεργασίες γνωρίζουν άμεσα η μία την ύπαρξη της άλλης (πχ στη χρήσηδιοχετεύσεων στο Unix) Σε αυτή την περίπτωση οι διεργασίες γνωρίζουν η μίατην άλλη κατrsquo όνομα και έχουν σχεδιασθεί για να εργάζονται μαζί για την επίτευξηενός στόχου Επομένως και εδώ έχουμε το φαινόμενο της συνεργασίας
1818
Ανταγωνισμός μεταξύ διεργασιώνOut = 4
In = 7
P0
gradesxls
essaydoc
progc
7
4
6
5
H διεργασία P0 διαβάζει την τιμή τηςμεταβλητής In που δείχνει στην επόμενηελεύθερη θέση στον κατάλογο των αρχείων πουπεριμένουν να εκτυπωθούν αλλά πρινολοκληρώσει την εργασία της διακόπτεται ηεκτέλεσή της και ξεκινά η εκτέλεση τηςδιεργασίας P1 η οποία αφού διαβάσει και αυτήτην τιμή της In αποθηκεύει στη θέση 7 τοαρχείο προς εκτύπωση και ανεβάζει την τιμήτης μεταβλητής In κατά μία μονάδα Τώραεπαναρχίζει η εκτέλεση της διεργασίας P0 απότο σημείο διακοπής της η οποία κάνονταςχρήση της παλιάς τιμής τής In τοποθετεί τοδικό της αρχείο στη θέση 7 αντί της 8 Αποτέλεσμα είναι ότι το αρχείο της διεργασίαςP1 δεν θα εκτυπωθεί ποτέ Όταν ένα τελικόαποτέλεσμα εξαρτάται από τη σειρά ήκαι τονχρόνο εκτέλεσης δύο ή περισσοτέρωνδιεργασιών τότε δημιουργούνται συνθήκεςανταγωνισμού (race conditions)
P1
1919
Αμοιβαίος αποκλεισμός
bull Για την αποφυγή συνθηκών ανταγωνισμού επιβάλλεται οαμοιβαίος αποκλεισμός (mutual exclusion) δηλαδή οαποκλεισμός μίας διεργασίας από κάποια ενέργεια πουταυτόχρονα επιτελεί κάποια άλλη διεργασία Αυτό οδηγείστον καθορισμό κάποιων μερών στον κώδικα μίαςδιεργασίας στα οποία η διεργασία αυτή προσπαθεί ναπροσπελάσει κοινά μεταξύ των διεργασιών αντικείμενα(πχ διαμοιραζόμενες περιοχές μνήμης αρχεία κλπ) Αυτά τα μέρη λέγονται κρίσιμα τμήματαπεριοχές (critical sectionsregions) και για τη σωστή εκτέλεσησυντρέχουσων διεργασιών πρέπει να διασφαλιστεί ότιποτέ δύο ή περισσότερες διεργασίες δεν θα βρίσκονταιταυτόχρονα στο ίδιο κρίσιμο τμήμα
2020
Προβλήματα σχετιζόμενα με τοναμοιβαίο αποκλεισμό
bull Αδιέξοδο (deadlock) όταν λχ δύο διεργασίεςχρειάζονται δύο πόρους και κάθε μία από τιςδιεργασίες έχει δεσμεύσει ένα από τους πόρουςκαι έχοντας αρχίσει οι διεργασίες να εκτελούν τοκρίσιμο τμήμα του κώδικά τους για τον δεύτεροπόρο ανακαλύπτουν ότι είναι δεσμευμένος απότην άλλη διεργασία
bull Παρατεταμένη στέρησης (starvation) όπουκάποια διεργασία δεν προλαβαίνει να δεσμεύσεικάποιον πόρο λόγω της πιο γρήγορηςδέσμευσής του από άλλες διεργασίες
2121
Έμμεση συνεργασία μεταξύδιεργασιών
bull Μέσω κοινής χρήσης κάποιων αντικειμένων (μεταβλητών αρχείων βάσεων δεδομένων κλπ) Τα προβλήματα του αμοιβαίουαποκλεισμού του αδιέξοδου και της παρατεταμένης στέρησηςυπάρχουν και εδώ Όμως τα κοινά αντικείμενα μπορούν ναπροσπελασθούν για διάβασμα ή για γράψιμο και μόνο στη δεύτερηπερίπτωση χρειάζεται αμοιβαίος αποκλεισμός Ένα άλλο πρόβλημαπου παρατηρείται εδώ και έχει να κάνει με την ενημέρωση κοινώνδεδομένων είναι αυτό της συνέπειας των δεδομένων (data coherence) όπως φαίνεται στο ακόλουθο παράδειγμαP0 a = a+1 a = a+1 (P0)
b = b+1 b = 2b (P1)P1 b = 2b b = b+1 (P0)
a = 2a a = 2a (P1)όπου το ζητούμενο είναι μετά το τέλος εκτέλεσης των εντολών ναισχύει η σχέση a = b Mε την εκτέλεση στα αριστερά αυτόεπιτυγχάνεται αλλά με την εκτέλεση στα δεξιά δεν επιτυγχάνεταιπαρόλο που οι δύο διεργασίες έχουν σεβασθεί τη συνθήκηαμοιβαίου αποκλεισμού για τις δύο μεταβλητές ξεχωριστά Αυτό πουχρειάζεται είναι και οι τέσσερεις εντολές να θεωρηθούν κρίσιμοτμήμα
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
1313
Ο μηχανισμός των συρρουτινών μεχρήση της εντολής RESUME mdash 3
1414
Πρόβλημα συντονισμού αριθμούδιαδικασιών
bull Να διαβαστούν κάρτες των 80 χαρακτήρων και νατυπωθούν σε γραμμές των 125 χαρακτήρων μετά δε τοτέλος κάθε κάρτας να εισαχθεί ένας κενός χαρακτήραςκαι κάθε δύο να αντικατασταθούν με ένα
bull Σειριακή λύση με 3 διαδικασίεςndash Διαδικασία 1η Διάβασε όλες τις κάρτες σε προσωρινή μνήμη
(buffer)ndash Διαδικασία 2η Κάνε τις αντικαταστάσειςndash Διαδικασία 3η Τύπωσε το αποτέλεσμα σε γραμμές των 125 χαρακτήρων
bull Μειονεκτήματα Χάσιμο χρόνου (overhead) λόγω του ότιεκτελούνται πολλές εντολές EE αλλά και μεγάλησπατάλη μνήμης
1515
Επίλυση με συρρουτίνεςchar rs spchar inbuf[80] outbuf[125]
void read() while (1)
READCARD (inbuf)for (int i=0 i lt 80 i++)
rs = inbuf [i]RESUME squash
rs = RESUME squash
void print() while (1)
for (int j = 0 j lt 125 j++) outbuf [j] = spRESUME squash
OUTPUT (outbuf)
void squash() while (1)
if (rs = ) sp = rsRESUME print
else
RESUME readif (rs == )
sp = RESUME print
else
sp = RESUME print sp = rsRESUME print
RESUME read
1616
Προβλήματα που δημιουργεί οταυτοχρονισμός (και ανάλογες
ευθύνες για το ΛΣ)bull Διαχείριση των εκτελούμενων διεργασιώνbull Βέλτιστη κατανομή των διαθέσιμων πόρων αναφορικάμε KME κύρια και περιφερειακή μνήμη και συσκευές EE μεταξύ των εκτελούμενων διεργασιών
bull Προστασία του περιβάλλοντος μίας διεργασίας από μηεπιτρεπτές παρεμβολές από τις άλλες διεργασίες
bull Ανεξαρτησία των αποτελεσμάτων που παράγει μίαδιεργασία από την ταχύτητα εκτέλεσής της σε σχέση μετις άλλες διεργασίες του συστήματος
bull Δυσκολία αποσφαλμάτωσης (debugging) τωνπρογραμμάτων γιατί συνήθως η εκτέλεση τωνδιεργασιών και η εν γένει συμπεριφορά του συστήματοςείναι μη προκαθορισμένη
1717
Αλληλεπίδραση μεταξύ διεργασιώνbull H ταυτόχρονη εκτέλεση περισσοτέρων της μίας διεργασιών οδηγεί συχνά σε
φαινόμενα αλληλεπίδρασης μεταξύ των διεργασιών αυτών (process interaction) που μπορούν να χωρισθούν σε 3 κατηγορίες ανάλογα με τοβαθμό στον οποίο μία διεργασία γνωρίζει την ύπαρξη άλλων διεργασιών καιπως τυχόν εκμεταλλεύεται αυτή την πληροφορία
ndash Οι διεργασίες δεν γνωρίζουν η μία την ύπαρξη της άλλης (πχ οι διεργασίες πουαντιστοιχούν στο κέλυφος ενός χρήστη στο Unix) Σε αυτή την περίπτωση δεντίθεται θέμα υποστήριξης διαδιεργασιακής επικοινωνίας αλλά μπορεί ναδημιουργηθεί πρόβλημα ανταγωνισμού (competition) δηλαδή προσπάθεια απότις εκτελούμενες διεργασίες να δεσμεύσουν τους ίδιους πόρους
ndash Οι διεργασίες γνωρίζουν έμμεσα η μία την ύπαρξη της άλλης (πχ μέσω τηςκοινής χρήσης ενός διαμοιραζόμενου αντικειμένου όπως λχ προσωρινή μνήμη(buffer)) Σε αυτή την περίπτωση δημιουργείται το φαινόμενο της συνεργασίας(cooperation) μέσω προσπέλασης στο κοινής χρήσης αντικείμενο
ndash Οι διεργασίες γνωρίζουν άμεσα η μία την ύπαρξη της άλλης (πχ στη χρήσηδιοχετεύσεων στο Unix) Σε αυτή την περίπτωση οι διεργασίες γνωρίζουν η μίατην άλλη κατrsquo όνομα και έχουν σχεδιασθεί για να εργάζονται μαζί για την επίτευξηενός στόχου Επομένως και εδώ έχουμε το φαινόμενο της συνεργασίας
1818
Ανταγωνισμός μεταξύ διεργασιώνOut = 4
In = 7
P0
gradesxls
essaydoc
progc
7
4
6
5
H διεργασία P0 διαβάζει την τιμή τηςμεταβλητής In που δείχνει στην επόμενηελεύθερη θέση στον κατάλογο των αρχείων πουπεριμένουν να εκτυπωθούν αλλά πρινολοκληρώσει την εργασία της διακόπτεται ηεκτέλεσή της και ξεκινά η εκτέλεση τηςδιεργασίας P1 η οποία αφού διαβάσει και αυτήτην τιμή της In αποθηκεύει στη θέση 7 τοαρχείο προς εκτύπωση και ανεβάζει την τιμήτης μεταβλητής In κατά μία μονάδα Τώραεπαναρχίζει η εκτέλεση της διεργασίας P0 απότο σημείο διακοπής της η οποία κάνονταςχρήση της παλιάς τιμής τής In τοποθετεί τοδικό της αρχείο στη θέση 7 αντί της 8 Αποτέλεσμα είναι ότι το αρχείο της διεργασίαςP1 δεν θα εκτυπωθεί ποτέ Όταν ένα τελικόαποτέλεσμα εξαρτάται από τη σειρά ήκαι τονχρόνο εκτέλεσης δύο ή περισσοτέρωνδιεργασιών τότε δημιουργούνται συνθήκεςανταγωνισμού (race conditions)
P1
1919
Αμοιβαίος αποκλεισμός
bull Για την αποφυγή συνθηκών ανταγωνισμού επιβάλλεται οαμοιβαίος αποκλεισμός (mutual exclusion) δηλαδή οαποκλεισμός μίας διεργασίας από κάποια ενέργεια πουταυτόχρονα επιτελεί κάποια άλλη διεργασία Αυτό οδηγείστον καθορισμό κάποιων μερών στον κώδικα μίαςδιεργασίας στα οποία η διεργασία αυτή προσπαθεί ναπροσπελάσει κοινά μεταξύ των διεργασιών αντικείμενα(πχ διαμοιραζόμενες περιοχές μνήμης αρχεία κλπ) Αυτά τα μέρη λέγονται κρίσιμα τμήματαπεριοχές (critical sectionsregions) και για τη σωστή εκτέλεσησυντρέχουσων διεργασιών πρέπει να διασφαλιστεί ότιποτέ δύο ή περισσότερες διεργασίες δεν θα βρίσκονταιταυτόχρονα στο ίδιο κρίσιμο τμήμα
2020
Προβλήματα σχετιζόμενα με τοναμοιβαίο αποκλεισμό
bull Αδιέξοδο (deadlock) όταν λχ δύο διεργασίεςχρειάζονται δύο πόρους και κάθε μία από τιςδιεργασίες έχει δεσμεύσει ένα από τους πόρουςκαι έχοντας αρχίσει οι διεργασίες να εκτελούν τοκρίσιμο τμήμα του κώδικά τους για τον δεύτεροπόρο ανακαλύπτουν ότι είναι δεσμευμένος απότην άλλη διεργασία
bull Παρατεταμένη στέρησης (starvation) όπουκάποια διεργασία δεν προλαβαίνει να δεσμεύσεικάποιον πόρο λόγω της πιο γρήγορηςδέσμευσής του από άλλες διεργασίες
2121
Έμμεση συνεργασία μεταξύδιεργασιών
bull Μέσω κοινής χρήσης κάποιων αντικειμένων (μεταβλητών αρχείων βάσεων δεδομένων κλπ) Τα προβλήματα του αμοιβαίουαποκλεισμού του αδιέξοδου και της παρατεταμένης στέρησηςυπάρχουν και εδώ Όμως τα κοινά αντικείμενα μπορούν ναπροσπελασθούν για διάβασμα ή για γράψιμο και μόνο στη δεύτερηπερίπτωση χρειάζεται αμοιβαίος αποκλεισμός Ένα άλλο πρόβλημαπου παρατηρείται εδώ και έχει να κάνει με την ενημέρωση κοινώνδεδομένων είναι αυτό της συνέπειας των δεδομένων (data coherence) όπως φαίνεται στο ακόλουθο παράδειγμαP0 a = a+1 a = a+1 (P0)
b = b+1 b = 2b (P1)P1 b = 2b b = b+1 (P0)
a = 2a a = 2a (P1)όπου το ζητούμενο είναι μετά το τέλος εκτέλεσης των εντολών ναισχύει η σχέση a = b Mε την εκτέλεση στα αριστερά αυτόεπιτυγχάνεται αλλά με την εκτέλεση στα δεξιά δεν επιτυγχάνεταιπαρόλο που οι δύο διεργασίες έχουν σεβασθεί τη συνθήκηαμοιβαίου αποκλεισμού για τις δύο μεταβλητές ξεχωριστά Αυτό πουχρειάζεται είναι και οι τέσσερεις εντολές να θεωρηθούν κρίσιμοτμήμα
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
1616
Προβλήματα που δημιουργεί οταυτοχρονισμός (και ανάλογες
ευθύνες για το ΛΣ)bull Διαχείριση των εκτελούμενων διεργασιώνbull Βέλτιστη κατανομή των διαθέσιμων πόρων αναφορικάμε KME κύρια και περιφερειακή μνήμη και συσκευές EE μεταξύ των εκτελούμενων διεργασιών
bull Προστασία του περιβάλλοντος μίας διεργασίας από μηεπιτρεπτές παρεμβολές από τις άλλες διεργασίες
bull Ανεξαρτησία των αποτελεσμάτων που παράγει μίαδιεργασία από την ταχύτητα εκτέλεσής της σε σχέση μετις άλλες διεργασίες του συστήματος
bull Δυσκολία αποσφαλμάτωσης (debugging) τωνπρογραμμάτων γιατί συνήθως η εκτέλεση τωνδιεργασιών και η εν γένει συμπεριφορά του συστήματοςείναι μη προκαθορισμένη
1717
Αλληλεπίδραση μεταξύ διεργασιώνbull H ταυτόχρονη εκτέλεση περισσοτέρων της μίας διεργασιών οδηγεί συχνά σε
φαινόμενα αλληλεπίδρασης μεταξύ των διεργασιών αυτών (process interaction) που μπορούν να χωρισθούν σε 3 κατηγορίες ανάλογα με τοβαθμό στον οποίο μία διεργασία γνωρίζει την ύπαρξη άλλων διεργασιών καιπως τυχόν εκμεταλλεύεται αυτή την πληροφορία
ndash Οι διεργασίες δεν γνωρίζουν η μία την ύπαρξη της άλλης (πχ οι διεργασίες πουαντιστοιχούν στο κέλυφος ενός χρήστη στο Unix) Σε αυτή την περίπτωση δεντίθεται θέμα υποστήριξης διαδιεργασιακής επικοινωνίας αλλά μπορεί ναδημιουργηθεί πρόβλημα ανταγωνισμού (competition) δηλαδή προσπάθεια απότις εκτελούμενες διεργασίες να δεσμεύσουν τους ίδιους πόρους
ndash Οι διεργασίες γνωρίζουν έμμεσα η μία την ύπαρξη της άλλης (πχ μέσω τηςκοινής χρήσης ενός διαμοιραζόμενου αντικειμένου όπως λχ προσωρινή μνήμη(buffer)) Σε αυτή την περίπτωση δημιουργείται το φαινόμενο της συνεργασίας(cooperation) μέσω προσπέλασης στο κοινής χρήσης αντικείμενο
ndash Οι διεργασίες γνωρίζουν άμεσα η μία την ύπαρξη της άλλης (πχ στη χρήσηδιοχετεύσεων στο Unix) Σε αυτή την περίπτωση οι διεργασίες γνωρίζουν η μίατην άλλη κατrsquo όνομα και έχουν σχεδιασθεί για να εργάζονται μαζί για την επίτευξηενός στόχου Επομένως και εδώ έχουμε το φαινόμενο της συνεργασίας
1818
Ανταγωνισμός μεταξύ διεργασιώνOut = 4
In = 7
P0
gradesxls
essaydoc
progc
7
4
6
5
H διεργασία P0 διαβάζει την τιμή τηςμεταβλητής In που δείχνει στην επόμενηελεύθερη θέση στον κατάλογο των αρχείων πουπεριμένουν να εκτυπωθούν αλλά πρινολοκληρώσει την εργασία της διακόπτεται ηεκτέλεσή της και ξεκινά η εκτέλεση τηςδιεργασίας P1 η οποία αφού διαβάσει και αυτήτην τιμή της In αποθηκεύει στη θέση 7 τοαρχείο προς εκτύπωση και ανεβάζει την τιμήτης μεταβλητής In κατά μία μονάδα Τώραεπαναρχίζει η εκτέλεση της διεργασίας P0 απότο σημείο διακοπής της η οποία κάνονταςχρήση της παλιάς τιμής τής In τοποθετεί τοδικό της αρχείο στη θέση 7 αντί της 8 Αποτέλεσμα είναι ότι το αρχείο της διεργασίαςP1 δεν θα εκτυπωθεί ποτέ Όταν ένα τελικόαποτέλεσμα εξαρτάται από τη σειρά ήκαι τονχρόνο εκτέλεσης δύο ή περισσοτέρωνδιεργασιών τότε δημιουργούνται συνθήκεςανταγωνισμού (race conditions)
P1
1919
Αμοιβαίος αποκλεισμός
bull Για την αποφυγή συνθηκών ανταγωνισμού επιβάλλεται οαμοιβαίος αποκλεισμός (mutual exclusion) δηλαδή οαποκλεισμός μίας διεργασίας από κάποια ενέργεια πουταυτόχρονα επιτελεί κάποια άλλη διεργασία Αυτό οδηγείστον καθορισμό κάποιων μερών στον κώδικα μίαςδιεργασίας στα οποία η διεργασία αυτή προσπαθεί ναπροσπελάσει κοινά μεταξύ των διεργασιών αντικείμενα(πχ διαμοιραζόμενες περιοχές μνήμης αρχεία κλπ) Αυτά τα μέρη λέγονται κρίσιμα τμήματαπεριοχές (critical sectionsregions) και για τη σωστή εκτέλεσησυντρέχουσων διεργασιών πρέπει να διασφαλιστεί ότιποτέ δύο ή περισσότερες διεργασίες δεν θα βρίσκονταιταυτόχρονα στο ίδιο κρίσιμο τμήμα
2020
Προβλήματα σχετιζόμενα με τοναμοιβαίο αποκλεισμό
bull Αδιέξοδο (deadlock) όταν λχ δύο διεργασίεςχρειάζονται δύο πόρους και κάθε μία από τιςδιεργασίες έχει δεσμεύσει ένα από τους πόρουςκαι έχοντας αρχίσει οι διεργασίες να εκτελούν τοκρίσιμο τμήμα του κώδικά τους για τον δεύτεροπόρο ανακαλύπτουν ότι είναι δεσμευμένος απότην άλλη διεργασία
bull Παρατεταμένη στέρησης (starvation) όπουκάποια διεργασία δεν προλαβαίνει να δεσμεύσεικάποιον πόρο λόγω της πιο γρήγορηςδέσμευσής του από άλλες διεργασίες
2121
Έμμεση συνεργασία μεταξύδιεργασιών
bull Μέσω κοινής χρήσης κάποιων αντικειμένων (μεταβλητών αρχείων βάσεων δεδομένων κλπ) Τα προβλήματα του αμοιβαίουαποκλεισμού του αδιέξοδου και της παρατεταμένης στέρησηςυπάρχουν και εδώ Όμως τα κοινά αντικείμενα μπορούν ναπροσπελασθούν για διάβασμα ή για γράψιμο και μόνο στη δεύτερηπερίπτωση χρειάζεται αμοιβαίος αποκλεισμός Ένα άλλο πρόβλημαπου παρατηρείται εδώ και έχει να κάνει με την ενημέρωση κοινώνδεδομένων είναι αυτό της συνέπειας των δεδομένων (data coherence) όπως φαίνεται στο ακόλουθο παράδειγμαP0 a = a+1 a = a+1 (P0)
b = b+1 b = 2b (P1)P1 b = 2b b = b+1 (P0)
a = 2a a = 2a (P1)όπου το ζητούμενο είναι μετά το τέλος εκτέλεσης των εντολών ναισχύει η σχέση a = b Mε την εκτέλεση στα αριστερά αυτόεπιτυγχάνεται αλλά με την εκτέλεση στα δεξιά δεν επιτυγχάνεταιπαρόλο που οι δύο διεργασίες έχουν σεβασθεί τη συνθήκηαμοιβαίου αποκλεισμού για τις δύο μεταβλητές ξεχωριστά Αυτό πουχρειάζεται είναι και οι τέσσερεις εντολές να θεωρηθούν κρίσιμοτμήμα
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
1919
Αμοιβαίος αποκλεισμός
bull Για την αποφυγή συνθηκών ανταγωνισμού επιβάλλεται οαμοιβαίος αποκλεισμός (mutual exclusion) δηλαδή οαποκλεισμός μίας διεργασίας από κάποια ενέργεια πουταυτόχρονα επιτελεί κάποια άλλη διεργασία Αυτό οδηγείστον καθορισμό κάποιων μερών στον κώδικα μίαςδιεργασίας στα οποία η διεργασία αυτή προσπαθεί ναπροσπελάσει κοινά μεταξύ των διεργασιών αντικείμενα(πχ διαμοιραζόμενες περιοχές μνήμης αρχεία κλπ) Αυτά τα μέρη λέγονται κρίσιμα τμήματαπεριοχές (critical sectionsregions) και για τη σωστή εκτέλεσησυντρέχουσων διεργασιών πρέπει να διασφαλιστεί ότιποτέ δύο ή περισσότερες διεργασίες δεν θα βρίσκονταιταυτόχρονα στο ίδιο κρίσιμο τμήμα
2020
Προβλήματα σχετιζόμενα με τοναμοιβαίο αποκλεισμό
bull Αδιέξοδο (deadlock) όταν λχ δύο διεργασίεςχρειάζονται δύο πόρους και κάθε μία από τιςδιεργασίες έχει δεσμεύσει ένα από τους πόρουςκαι έχοντας αρχίσει οι διεργασίες να εκτελούν τοκρίσιμο τμήμα του κώδικά τους για τον δεύτεροπόρο ανακαλύπτουν ότι είναι δεσμευμένος απότην άλλη διεργασία
bull Παρατεταμένη στέρησης (starvation) όπουκάποια διεργασία δεν προλαβαίνει να δεσμεύσεικάποιον πόρο λόγω της πιο γρήγορηςδέσμευσής του από άλλες διεργασίες
2121
Έμμεση συνεργασία μεταξύδιεργασιών
bull Μέσω κοινής χρήσης κάποιων αντικειμένων (μεταβλητών αρχείων βάσεων δεδομένων κλπ) Τα προβλήματα του αμοιβαίουαποκλεισμού του αδιέξοδου και της παρατεταμένης στέρησηςυπάρχουν και εδώ Όμως τα κοινά αντικείμενα μπορούν ναπροσπελασθούν για διάβασμα ή για γράψιμο και μόνο στη δεύτερηπερίπτωση χρειάζεται αμοιβαίος αποκλεισμός Ένα άλλο πρόβλημαπου παρατηρείται εδώ και έχει να κάνει με την ενημέρωση κοινώνδεδομένων είναι αυτό της συνέπειας των δεδομένων (data coherence) όπως φαίνεται στο ακόλουθο παράδειγμαP0 a = a+1 a = a+1 (P0)
b = b+1 b = 2b (P1)P1 b = 2b b = b+1 (P0)
a = 2a a = 2a (P1)όπου το ζητούμενο είναι μετά το τέλος εκτέλεσης των εντολών ναισχύει η σχέση a = b Mε την εκτέλεση στα αριστερά αυτόεπιτυγχάνεται αλλά με την εκτέλεση στα δεξιά δεν επιτυγχάνεταιπαρόλο που οι δύο διεργασίες έχουν σεβασθεί τη συνθήκηαμοιβαίου αποκλεισμού για τις δύο μεταβλητές ξεχωριστά Αυτό πουχρειάζεται είναι και οι τέσσερεις εντολές να θεωρηθούν κρίσιμοτμήμα
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
2222
lsquoΑμεση συνεργασία μεταξύδιεργασιών
bull Μέσω άμεσης επικοινωνίας με τη χρήσημηνυμάτων (messages) Λόγω της έλλειψηςκοινού αντικειμένου επικοινωνίας δεν τίθεταιθέμα αμοιβαίου αποκλεισμού αλλά τα άλλαπροβλήματα παραμένουν Αδιέξοδο μπορεί ναδημιουργηθεί αν μία διεργασία περιμένει μήνυμααπό μία άλλη και αντίστροφα Παρατεταμένηστέρηση μπορεί να προκύψει αν λχ μίαδιεργασία P0 προσπαθεί να επικοινωνήσει με 2 άλλες διεργασίες P1 και P2 και αυτές με την P0η P0 μπορεί να επικοινωνεί συνεχώς με την P1και η P2 να μην καταφέρει ποτέ να ανταλλάξειμηνύματα με την P0
2323
Συμβολισμοί για ταυτόχρονηεπεξεργασία
bull Για την υποστήριξη ταυτόχρονης επεξεργασίας στοεπίπεδο λογισμικού θα πρέπει οι γλώσσεςπρογραμματισμού να έχουν συμβολισμούς που ναδηλώνουν ταυτοχρονία διαδιεργασιακή επικοινωνία κρίσιμα τμήματα κλπ Κάθε γλώσσα προγραμματισμούέχει τους δικούς της συμβολισμούς αλλά για μία γλώσσαβασισμένη στο δομημένο προγραμματισμό μπορούμε ναθεωρήσουμε την ύπαρξη ενός συμβολισμού του τύπουparbeginhellipparend όπου όλες οι εντολές μέσα στομπλοκ εκτελούνται ταυτόχρονα
bull Επίσης μπορούμε να θεωρήσουμε την υποστήριξη δύοενσωματωμένων συναρτήσεων enter_critical(R)και exit_critical(R) για τον προσδιορισμόκρίσιμων τμημάτων αναφορικά με κάποιον πόρο R
2424
Γενική μοντελοποίηση αμοιβαίουαποκλεισμού
PROCESS 0
void P0 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
parbeginP0() P1() hellip Pn()
parend
PROCESS 1
void P1 while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
PROCESS n
void Pn while (1) preceding code
enter_critical(R) critical section
exit_critical(R) following code
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
2525
Εφαρμογή του μοντέλου στο σενάριοτης άμεσης συνεργασίας μεταξύ
διεργασιώνmain()
int a = 1 b = 1
void P0(int ab) void P1(int ab) enter_critical(ab) enter_critical(ab)a = a+1 b = 2bb = b+1 a = 2aexit_critical(ab)exit_critical(ab)
parbeginP0(ab)P1(ab)
parend
2626
Σωστή υλοποίηση του αμοιβαίουαποκλεισμού
bull Για τη σωστή υποστήριξη του αμοιβαίου αποκλεισμού πρέπει ναικανοποιούνται οι ακόλουθες προϋποθέσειςndash Όλες οι διεργασίες υπόκεινται στον περιορισμό του αμοιβαίου
αποκλεισμού δηλαδή μόνο μία διεργασία ανά πάσα στιγμή μπορεί ναβρίσκεται στο κρίσιμο τμήμα της που αντιστοιχεί σε κάποιοσυγκεκριμένο πόρο ή κοινό αντικείμενο
ndash Αν μία διεργασία διακοπεί όταν δεν βρίσκεται σε κρίσιμο τμήμα θαπρέπει να το κάνει με τρόπο που να μην επηρεάζει άλλες διεργασίες
ndash Δεν επιτρέπεται η επrsquo αόριστον αναμονή μίας διεργασίας για να εισέλθεισε κρίσιμο τμήμα δηλαδή φαινόμενα αδιέξοδου ή παρατεταμένηςστέρησης
ndash Αν μία διεργασία δεν βρίσκεται σε κάποιο κρίσιμο τμήμα δεν μπορεί νααπαγορέψει σε άλλη διεργασία την είσοδο στο κρίσιμο τμήμα
ndash Δεν επιτρέπονται υποθέσεις σε ότι αφορά την ταχύτητα εκτέλεσης τωνδιεργασιών ή το πλήθος των επεξεργαστών στο σύστημα
ndash Δεν επιτρέπεται η επrsquo αόριστον παραμονή μίας διεργασίας σε κρίσιμοτμήμα
2727
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
2828
Υλοποίηση αμοιβαίου αποκλεισμούσε επίπεδο λογισμικού
bull Η υλοποίηση του αμοιβαίου αποκλεισμού γίνεται με τη χρήσηκατάλληλων τεχνικών προγραμματισμού σε οποιαδήποτε γλώσσαπρογραμματισμού
bull Δεν προϋποθέτει την ύπαρξη στο σύστημα ειδικών μηχανισμώνυποστήριξης του αμοιβαίου αποκλεισμού
bull Οι τεχνικές αυτές είναι γνωστές ως οι αλγόριθμοι των Dekker καιPeterson Ο αλγόριθμος του Ολλανδού μαθηματικού Dekker χρησιμοποιήθηκε από τον E W Dijkstra το 1965
bull Βασίζονται στο γεγονός ότι το σύστημα κατά κανόνα υποστηρίζει μίαστοιχειώδη μορφή αμοιβαίου αποκλεισμού στο επίπεδο πρόσβασηςσε κάποια θέση μνήμης δηλαδή ότι ανά πάσα στιγμή μόνο μίααίτηση για διάβασμα ή γράψιμο σε κάποια θέση μνήμης μπορεί ναεξυπηρετηθεί και αν τυχόν υπάρχουν και άλλες τέτοιες αιτήσεις γιααυτή τη θέση μνήμης πρέπει να σειριοποιηθούν
bull Η παρουσίαση των αλγόριθμων γίνεται για την περίπτωση 2 διεργασιών αλλά γενικεύονται και για την περίπτωση οποιουδήποτεπλήθους Ν διεργασιών
2929
Αλγόριθμος του Dekker Πρώτηπροσπάθεια
int turn = 0
void P0() void P1() while (1) while (1)
preceding code preceding code while (turn = 0) while (turn = 1)
do nothing do nothing critical section critical section turn = 1 turn = 0 following code following code
void main()parbegin P0() P1() parend
3030
Προβλήματα με την πρώτηπροσέγγιση
bull Απαιτεί την αυστηρή εναλλαγή (strict alteration) τωνδιεργασιών κατά την εισαγωγή τους στα κρίσιμα τμήματαμε αποτέλεσμα η γρηγορότερη από τις διεργασίες νακαθυστερείται από την πιο αργή (από πλευράς χρήσηςτου κρίσιμου τμήματος) διεργασία Αν λχ η P0χρησιμοποιεί την KME μόνο μία φορά την ώρα και η P11000 φορές τότε η P1 είναι αναγκασμένη να ακολουθείτον ρυθμό της πολύ πιο αργής P0 διότι δεν είναιδυνατόν να χρησιμοποιήσει ξανά η P1 την KME πριν τοχρησιμοποιήσει η P0
bull Αν μία από τις διεργασίες διακοπεί λόγω κάποιουλάθους σε οποιοδήποτε μέρος του κώδικα (είτε μέσα είτεέξω από το κρίσιμο τμήμα της) τότε η άλλη μπλοκάρεταιεπrsquo άπειρον
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
3131
Αλγόριθμος του Dekker Δεύτερηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code while (flag[1]) while (flag[0])
do nothing do nothing flag[0] = true flag[1] = true critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3232
Προβλήματα με τη δεύτερηπροσέγγιση
bull Το λάθος της πρώτης προσέγγισης ήταν ότι κρατούντο πληροφορίεςμόνο για τη διεργασία που μπορούσε να εισέλθει στο κρίσιμο τμήμα ενώ χρειάζονται πληροφορίες και για τις δύο διεργασίες
bull Αυτό επιτυγχάνεται με τη δεύτερη λύση (μέσω των μεταβλητώνflag) και έτσι μία διεργασία και μπορεί να εισέλθει στο κρίσιμοτμήμα όσες φορές θέλει και δεν μπλοκάρεται αν ή άλλη διακόψει τηνεκτέλεσή της
bull Ας θεωρήσουμε όμως την ακόλουθη σειρά εκτέλεσης των εντολώντων δύο διεργασιώνndash Η P0 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[1] είναι falsendash Η P1 εκτελεί το (δεύτερο) while και βρίσκει ότι η flag[0] είναι falsendash Η P0 θέτει flag[0] = true και μπαίνει στο κρίσιμο τμήμαndash Η P1 θέτει flag[1] = true και μπαίνει στο κρίσιμο τμήμα
bull Δηλαδή αυτή η προσέγγιση δεν υποστηρίζει καν αμοιβαίοαποκλεισμό
3333
Αλγόριθμος του Dekker Τρίτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
3434
Προβλήματα με την τρίτηπροσέγγιση
bull Το πρόβλημα με την δεύτερη προσέγγιση έγκειται στο ότι μίαδιεργασία μπορεί να αλλάξει την κατάσταση τής μεταβλητής τηςαφού η άλλη την έχει ελέγξει
bull H τρίτη προσέγγιση προσπαθεί να λύσει το πρόβλημα πουαναφέρθηκε προηγουμένως με το να υποχρεώνει κάθε διεργασία ναδηλώσει πρώτα ότι επιθυμεί να εισέλθει στο κρίσιμο τμήμα της πριναρχίσει να εκτελεί το βρόχο (ουσιαστικά εναλλάσσουμε τις δύογραμμές κώδικα)
bull Αν και πράγματι αυτή η λύση υποστηρίζει σωστά τον αμοιβαίοαποκλεισμό εν τούτοις εισαγάγει το πρόβλημα του αδιέξοδου ανπρολάβουν και οι δύο διεργασίες να θέσουν την τιμή της αντίστοιχηςμεταβλητής τους σε true πριν προλάβει η άλλη διεργασία νααρχίσει να εκτελεί το βρόχο while τότε η κάθε μία θα νομίζει ότι ηάλλη έχει ήδη αρχίσει να εκτελεί το κρίσιμο τμήμα της και δεν θαεισέλθει ποτέ σε αυτό
3535
Αλγόριθμος του Dekker Τέταρτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
flag[0] = false flag[1] = falsedelay(random_num) delay(random_num)flag[0] = true flag[1] = true
critical section critical section flag[0] = false flag[1] = false following code following code
void main()parbegin P0() P1() parend
3636
Προβλήματα με την τέταρτηπροσέγγιση
bull Το πρόβλημα με την τρίτη προσέγγιση είναι ότι η κάθε διεργασία επιμένει να θέλει ναεισέλθει στο κρίσιμο τμήμα και δεν έχει τη δυνατότητα να υποχωρήσει
bull H τέταρτη παραλλαγή προσπαθεί να επιλύσει το πρόβλημα με το να αναγκάζει μίαδιεργασία που έχει δηλώσει ότι θέλει να εκτελέσει το κρίσιμο τμήμα της να κάνει πίσωγια ένα μικρό αλλά τυχαίο χρονικό διάστημα αν το ίδιο επιθυμεί και η άλλη διεργασία
bull Παρrsquo όλο που φαίνεται ότι η παραλλαγή αυτή λύνει όλα τα προβλήματα χωρίς ναδημιουργεί καινούργια εν τούτοις αυτό δεν αληθεύει Όσο και αν είναι απίθανο είναιωστόσο δυνατόν να δημιουργηθεί το πρόβλημα της επrsquo αόριστον αναμονής (indefinite postponement) αν δημιουργηθεί η ακόλουθη αλληλουχία εκτέλεσης εντολών
ndash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = truendash Η P0 ελέγχει την τιμή της flag[1]ndash Η P1 ελέγχει την τιμή της flag[0]ndash H P0 θέτει flag[0] = falsendash Η P1 θέτει flag[1] = falsendash H P0 θέτει flag[0] = truendash Η P1 θέτει flag[1] = true
bull Αυτή η αλληλουχία εκτέλεσης εντολών μπορεί να συνεχίσει επrsquo άπειρον χωρίς καμίααπό τις δύο διεργασίες να μπορέσει ποτέ να εκτελέσει το κρίσιμο τμήμα της
bull Το φαινόμενο αυτό είναι γνωστό ως ενεργό αδιέξοδο (livelock)
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
3737
Αλγόριθμος του Dekker Πέμπτηπροσπάθεια
enum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = truewhile (flag[1]) while (flag[0])
if (turn == 1) if (turn == 0) flag[0] = false flag[1] = falsewhile (turn == 1) while (turn == 0)
do nothing do nothing flag[0] = true flag[1] = true
critical section critical section turn = 1 turn = 0flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false turn = 0 parbegin P0() P1() parend
3838
Αντιμετώπιση των προβλημάτωνστην πέμπτη προσέγγιση
bull Η τελευταία αυτή προσέγγιση επιλύει ταπροηγούμενα προβλήματα με το να συνδυάζειτις τεχνικές των προηγούμενων προσεγγίσεων
bull Συγκεκριμένα χρησιμοποιεί και τη μεταβλητήturn η οποία δηλώνει ποια διεργασία έχειπροτεραιότητα να εισέλθει στο κρίσιμο τμήμα καιτις μεταβλητές flag που χρησιμοποιούν οιδιεργασίες για να δηλώσουν ότι θέλουν να έχουνπρόσβαση στο κρίσιμο τμήμα
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται ως άσκηση
3939
Αλγόριθμος του Petersonenum boolean (false = 0 true = 1)boolean flag[2] = (0 0)int turn
void P0() void P1() while (true) while (true)
preceding code preceding code flag[0] = true flag[1] = trueturn = 1 turn = 0while (flag[1] ampamp turn == 1) while (flag[0] amp turn == 0)
do nothing do nothing critical section critical section flag[0] = false flag[1] = false following code following code
void main () flag [0] = false flag [1] = false parbegin P0() P1() parend
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
4040
Σύγκριση των δύο αλγορίθμων
bull Ο αλγόριθμος του Peterson (που δημοσιεύτηκε16 χρόνια μετά τη δημοσίευση του αλγόριθμουτου Dekker) είναι πιο απλός
bull Η βασική φιλοσοφία του αλγόριθμου είναι ότιενώ μία διεργασία δηλώνει την πρόθεσή της ναεισέλθει στο κρίσιμο τμήμα αφήνει την άλλη ναπράξει αυτό αν το επιθυμεί
bull Το πώς ακριβώς επιτυγχάνεται η σωστήυλοποίηση του αμοιβαίου αποκλεισμού αφήνεται και πάλι ως άσκηση
4141
Τα μειονεκτήματα της πρώτηςκατηγορίας τεχνικών υλοποίησης
του αμοιβαίου αποκλεισμούbull Όλοι οι αλγόριθμοι υλοποίησης του αμοιβαίουαποκλεισμού με προγραμματιστικές τεχνικές βασίζονταιστη συνεχή εξέταση της τιμής μίας ή περισσοτέρωνμεταβλητών
bull Αυτό επιτυγχάνεται με τη δημιουργία ατέρμονων βρόχωνοι οποίοι εκτελούνται συνέχεια σπαταλώντας το χρόνοτης ΚΜΕ
bull Αυτό το φαινόμενο ονομάζεται ενεργός αναμονή (busy waiting) και είναι το πιο σοβαρό μειονέκτημα αυτής τηςκατηγορίας τεχνικών
bull Επίσης οι αλγόριθμοι αυτοί δεν είναι πάντα εύκολο ναεπεκταθούν στη γενική περίπτωση Ν διεργασιών δημιουργώντας πολύπλοκο κώδικα ο οποίος είναιδύσκολο να αποσφαλματωθεί
4242
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικού
bull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
4343
Απενεργοποίηση διακοπτών
bull Ξεκινώντας από τη σκέψη ότι αυτό που διακόπτει τηλειτουργία μίας διεργασίας είναι οι διακόπτες (interrupts) θα μπορούσαμε να επιτύχουμε αμοιβαίο αποκλεισμό μετην απενεργοποίησή τους πριν την είσοδο στο κρίσιμοτμήμα και την επανεργοποίησή τους όταν η διεργασίαεξέλθει από το κρίσιμο τμήμα
bull Η τεχνική αυτή δεν λειτουργεί σε συστήματα με πολλούςεπεξεργαστές
bull Επίσης έχει μεγάλο κόστος στην εφαρμογή της επειδήκατά το διάστημα που οι διακόπτες είναιαπενεργοποιημένοι πολλές λειτουργίες του συστήματοςδεν μπορούν να εκτελεσθούν
4444
Υλοποίηση αμοιβαίου αποκλεισμούμε χρήση διακοπτών
while (1) preceding code disable interrupts critical section enable interrupts following code
4545
Ειδικές εντολές χαμηλού επιπέδουπου υλοποιούνται στο επίπεδο της
μηχανήςbull TestampSetbull CompareampSwap
ndash Επίσης γνωστή και ως ldquocompare and exchangerdquo
bull Exchange
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
4646
TestampSetboolean testset (int i)if (i == 0)
i = 1return true
else
return false
bull Αν η τιμή τηςπαραμέτρου i είναι 0 τοκάνει 1 και δηλώνειεπιτυχή εκτέλεση και αν ητιμή της i είναι 1 απλάδηλώνει αποτυχία
bull Ο έλεγχος και αλλαγή τηςτιμής της i γίνονται σαναδιαίρετες (indivisible) πράξεις
4747
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
TestampSetconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (testset(bolt))
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
4848
CompareampSwap
int compare_and_swap (int word int testvalint newval)
int oldvaloldval = wordif (oldval == testval)
word = newvalreturn oldval
bull Ελέγχει τη τιμή μίας θέσηςμνήμης (word) σε σχέση μετην τιμή της παραμέτρουtestval
bull Αν οι τιμές είναι οι ίδιες τότε ηθέση μνήμης παίρνει την τιμήτης παραμέτρου newval διαφορετικά δεν υπάρχειτροποποίηση
bull Η συνάρτηση επιστρέφει τηνπαλιά τιμή της θέσης μνήμης επομένως η θέση μνήμης έχειαλλάξει τιμή αν η τιμή πουεπιστρέφεται είναι η ίδια με τηντιμή της testval
bull Οι πράξεις είναι και πάλιαδιαίρετες
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
4949
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
CompareampSwapconst int n = N no of processes int bolt
void P(int i)while (1) preceding code while (compare_and_swap(bolt01) == 1)
do nothing critical section bolt = 0 following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητήbolt χρησιμοποιείται μεαρχική τιμή 0
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτήπου βρίσκει τη bolt μετιμή 0 (και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτήπου βρίσκεται στο κρίσιμοτμήμα εξέλθει και αλλάξειτην τιμή του bolt πάλι σε0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένειστο δεύτερο while θαμπορέσει να εισέλθει στοκρίσιμο τμήμα
5050
Exchange
void exchange (int registerint memory)
int temptemp = memorymemory = registerregister = temp
bull Ανταλλάσσει τα περιεχόμενατου καταχωρητή register μεαυτά της θέσης μνήμηςmemory
bull Όσο διαρκεί η εκτέλεση τηςεντολής η θέση μνήμης είναικλειδωμένη και δεν μπορεί ναπροσπελασθεί από κανένανάλλο
bull Οι αρχιτεκτονικές Intel IA-32 (Pentium) και IA-64 (Itanium)υποστηρίζουν μία τέτοιαεντολή (XCHG)
5151
Υλοποίηση του αμοιβαίουαποκλεισμού με την εντολή
Exchangeconst int n = N no of processes int bolt
void P(int i)int keyiwhile (1) preceding code keyi = 1while (keyi = 0)
exchange(keyibolt) critical section exchange(keyibolt) following code
void main()bolt = 0parbegin P(0) P(1) hellip P(n) parend
bull Μία κοινή μεταβλητή boltχρησιμοποιείται με αρχικήτιμή 0
bull Κάθε διεργασία έχει μίατοπική μεταβλητή keyi μεαρχική τιμή 1
bull Η μόνη διεργασία πουμπορεί να εισέλθει στοκρίσιμο τμήμα είναι αυτή πουβρίσκει τη bolt με τιμή 0(και την κάνει 1)
bull Όλες οι άλλες διεργασίεςπεριμένουν στο δεύτεροwhile έως ότου αυτή πουβρίσκεται στο κρίσιμο τμήμαεξέλθει και αλλάξει την τιμήτου bolt πάλι σε 0
bull Μόνο τότε κάποια από τιςδιεργασίες που περιμένει στοδεύτερο while θα μπορέσεινα εισέλθει στο κρίσιμοτμήμα
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
5252
Πλεονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Επεκτείνονται εύκολα για την περίπτωσηπερισσοτέρων των δύο διεργασιών ακόμα καιστην περίπτωση συστημάτων με πολλούςεπεξεργαστές Μοναδική προϋπόθεση είναι ηύπαρξη κοινής μνήμης
bull Είναι απλές σαν έννοιες και επομένως εύκολο ναελεγχθεί η σωστή χρήση τους
bull Μπορούν να υποστηρίξουν πολλαπλά κρίσιματμήματα με τη χρήση διαφορετικών μεταβλητών
5353
Μειονεκτήματα της υλοποίησης τουαμοιβαίου αποκλεισμού σε επίπεδο
υλικούbull Γίνεται χρήση ενεργούς αναμονής με αρνητικό αποτέλεσμα το
σχετικό κόστοςbull Δεν αποφεύγεται το πρόβλημα της παρατεταμένης στέρησης γιατί
κάποια διεργασία μπορεί να περιμένει επrsquo άπειρον να εκτελέσει τοκρίσιμο τμήμα της
bull Επίσης είναι πιθανή η δημιουργία αδιέξοδου στην περίπτωση πουμία διεργασία ενώ βρίσκεται στο κρίσιμο τμήμα της διακοπεί από μίαάλλη διεργασία μεγαλύτερης προτεραιότητας Πχ αν σε ένασύστημα με ένα επεξεργαστή μία διεργασία P0 ενώ βρίσκεται σε ένακρίσιμο τμήμα διακοπεί από μία άλλη διεργασία P1 η οποία έχειμεγαλύτερη προτεραιότητα από την P0 τότε αν επιπλέον η P1προσπαθήσει να εισέλθει στο ίδιο κρίσιμο τμήμα θα έχουμεαδιέξοδο η P1 δεν θα μπορεί να συνεχίσει αλλά λόγω του ότι έχειμεγαλύτερη προτεραιότητα δεν θα αφήσει και την P0 να συνεχίσει
5454
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικού
bull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
5555
Σημαφόρος
bull Η βασική αρχή στηρίζεται στη χρήση σημάτων (signals) δύο ή περισσότερες διεργασίες μπορούν νασυγχρονίσουν την εκτέλεσή τους μέσω της αποστολήςκαι αναμονής λήψης σημάτων
bull Για την αποστολή και καταμέτρηση των σημάτωνχρησιμοποιούνται ειδικές μεταβλητές που λέγονταισημαφόροι (semaphores) σε συνδυασμό με δύοθεμελιώδεις λειτουργίες semWait και semSignal
bull Οι σημαφόροι προτάθηκαν από τον Dijkstra το 1965
5656
Πράξεις με σημαφόρους
bull Μόνο 3 πράξεις μπορούν να γίνουν με ένα σημαφόροndash Αρχικοποίηση με μία θετική ακέραια τιμήndash Μείωση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semWait Αν η τιμή του σημαφόρου γίνει αρνητική ηδιεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή τηςκαι μπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενημε το σημαφόρο Διαφορετικά η διεργασία συνεχίζει τηνεκτέλεσή της
ndash Αύξηση της τιμής του σημαφόρου κατά μία μονάδα με χρήση τηςεντολής semSignal Αν η τιμή του σημαφόρου δεν είναι θετική τότε μία από τις διεργασίες που βρίσκονται υπό αναστολή στηνουρά του σημαφόρου (αν υπάρχουν τέτοιες διεργασίες) αλλάζειτην κατάστασή της σε έτοιμη για εκτέλεση
ndash Εξυπακούεται ότι η εκτέλεση των εντολών semWait καιsemSignal θα πρέπει να γίνεται ως ατομική ενέργεια (atomic action)
5757
Ορισμός σημαφόρουstruct semaphore
int countqueueType queue
void semWait(semaphore s)scount--if (scount lt 0)
place this process in squeue block this process
void semSignal(semaphore s)scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
5858
Δυαδικός σημαφόρος
bull Οι δυαδικοί σημαφόροι (binary semaphores) μπορούν ναπάρουν μόνο τις τιμές 0 και 1 είναι πιο εύκολο ναυλοποιηθούν από τους γενικούς σημαφόρους και μπορείνα αποδειχθεί ότι έχουν την ίδια εκφραστική ικανότητα μετους γενικούς σημαφόρους
bull Μία παρόμοια έννοια είναι αυτή του μηχανισμού mutex Η βασική διαφορά από τον δυαδικό σημαφόρο είναι ότι ηδιεργασία που κλειδώνει το mutex (θέτει την τιμή του σε0) είναι η μόνη που μπορεί να το ξεκλειδώσει (να θέσειτην τιμή του σε 1) Στους δυαδικούς σημαφόρους δενισχύει αυτός ο περιορισμός και η διεργασία πουξεκλειδώνει ένα δυαδικό σημαφόρο δεν είναι κατrsquo ανάγκηη ίδια που τον κλείδωσε
5959
Πράξεις με δυαδικούς σημαφόρουςbull Όπως και προηγουμένως μόνο 3 πράξεις μπορούν να γίνουν με
ένα δυαδικό σημαφόροndash Αρχικοποίηση σε 0 ή 1ndash Η εντολή semWaitB ελέγχει την τιμή του σημαφόρου Αν αυτή είναι ήδη
0 η διεργασία που εκτέλεσε την εντολή αναστέλλει την εκτέλεσή της καιμπαίνει σε μία ουρά υπό αναστολή διεργασιών σχετιζόμενη με τοσημαφόρο Διαφορετικά αν η τιμή του σημαφόρου είναι 1 τότε γίνεται 0 και η διεργασία συνεχίζει την εκτέλεσή της
ndash Η εντολή semSignalB ελέγχει αν υπάρχουν διεργασίες υπό αναστολήστην ουρά του σημαφόρου (με τιμή σημαφόρου 0) Αν υπάρχουν τότεμία από αυτές αλλάζει την κατάστασή της σε έτοιμη για εκτέλεση Αν ηουρά είναι άδεια τότε η τιμή του σημαφόρου γίνεται 1 Αν η τιμή τουσημαφόρου είναι ήδη 1 και η ουρά του άδεια η εκτέλεση τηςsemSignalB δεν έχει κανένα αποτέλεσμα
ndash Και πάλι η εκτέλεση των εντολών semWaitB και semSignalB θαπρέπει να γίνεται ως ατομική ενέργεια
6060
Ορισμός δυαδικού σημαφόρουstruct binary_semaphore
enum zero one valuequeueType queue
void semWaitB(binary_semaphore s)if (svalue == one)
svalue = zeroelse
place this process in squeue block this process
void semSignalB(semaphore s)if (squeue is empty())
svalue = oneelse
remove a process P from squeue place process P on ready list
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
6161
ΙσχυρόςΑδύνατος σημαφόρος
bull Αν ο ορισμός του σημαφόρου καθορίζει ότι ηδιεργασία που θα ενεργοποιηθεί είναι η πρώτηστην ουρά (δηλαδή αυτή που βρίσκεται υπόαναστολή το μεγαλύτερο χρονικό διάστημα) τότεο σημαφόρος αυτός λέγεται ισχυρός (strong semaphore)
bull Στην αντίθετη περίπτωση που δεν καθορίζεται ησειρά ενεργοποίησης των διεργασιών οσημαφόρος αυτός λέγεται αδύνατος (weak semaphore)
6262
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 1
6363
Παράδειγμα λειτουργίας ενόςισχυρού σημαφόρου mdash 2
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
6464
Υλοποίηση σημαφόρωνbull Όπως ήδη ειπώθηκε είναι πολύ σημαντικό η εκτέλεση των εντολών
semaWait και semaSignal (ή semaWaitB και semaSignalB γιαδυαδικούς σημαφόρους) να γίνεται ως ατομική πράξη
bull Μία ευνόητη λύση είναι η υλοποίησή τους στο υλικό ή ως μικροπρόγραμμαbull Αν αυτό δεν είναι δυνατόν μπορούν να χρησιμοποιηθούν κάποιες από τις
προαναφερθείσες προσεγγίσεις υλοποίησης του αμοιβαίου αποκλεισμού δεδομένου ότι η ουσία του προβλήματος είναι ακριβώς αυτή μόνο μίαδιεργασία μπορεί ανά πάσα χρονική στιγμή να έχει πρόσβαση σε ένασημαφόρο μέσω εκτέλεσης των εντολών του
bull Μπορούν επομένως να χρησιμοποιηθούν τεχνικές τύπου Dekker καιPeterson αν και το κόστος θα είναι σημαντικό
bull Εναλλακτικά μπορούν να χρησιμοποιηθούν οι εντολές που εξετάστηκανστο επίπεδο του υλικού ή η τεχνική της απενεργοποίησης διακοπτών
bull Αν και εδώ έχουμε χρήση ενεργούς αναμονής το κόστος είναι περιορισμένολόγω του ότι το μέγεθος του κρίσιμου τμήματος (πρόσβαση σε μία ακέραιαμεταβλητή και μία ουρά και εκτέλεση λίγων απλών εντολών) είναι πολύμικρό
6565
Υλοποίηση σημαφόρων με τηνεντολή CompareampSwap
semWait(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount--if (scount lt 0)
place this process in squeue block this process (must also set sflag to 0)
sflag = 0
semSignal(s)while (compare_and_swap(sflag 0 1) == 1)
do nothing scount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
sflag = 0
6666
Υλοποίηση σημαφόρων μέσωαπενεργοποίησης των διακοπτών
semWait(s)inhibit interruptsscount--if (scount lt 0)
place this process in squeue block this process and allow interrupts
else
allow interrupts
semSignal(s)inhibit interruptsscount++if (scount lt= 0)
remove a process P from squeue place process P on ready list
allow interrupts
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
6767
Υλοποίηση του αμοιβαίουαποκλεισμού με σημαφόρους
const int n = Ν number of processes semaphore s = 1
void P(int i)while (1) preceding code semWait(s) critical section semSignal(s) folowing code
void main()parbegin P(0) P(1) hellip P(n) parend
6868
Σενάριο πρόσβασης σε κοινό πόροαπό 3 διεργασίες μέσω σημαφόρου
69
Συμπεριφορά των διεργασιών στοπροηγούμενο σενάριο
bull Ο κοινός πόρος προστατεύεται από το σημαφόρο lockbull Η διεργασία A εκτελεί την εντολή semWait(lock)
ndash Επειδή lock==1 η A εισέρχεται στο κρίσιμο τμήμα της καιlock=0
bull Όσο η A βρίσκεται μέσα στο κρίσιμο τμήμα της οιδιεργασίες B και C εκτελούν την εντολή semWait(lock)και τίθενται υπό αναστολή
bull Όταν η A εξέλθει από το κρίσιμο τμήμα της και εκτελέσειτην εντολή semSignal(lock) η B που ήταν η πρώτηαπό τις άλλες δύο διεργασίες που τέθηκε υπό αναστολή(επομένως είναι η πρώτη στην ουρά του lock) μπορείτώρα να εισέλθει στο κρίσιμο τμήμα της
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
70
Πλεονεκτήματα των σημαφόρωνέναντι προηγούμενων τεχνικών
bull Ελαχιστοποίηση του κόστους που συνεπάγεται η ενεργός αναμονή˙οι διεργασίες που δεν μπορούν να εισέλθουν στο κρίσιμο τμήματίθενται υπό αναστολή και δεν σπαταλούν πόρους του συστήματος
bull Η απρόσμενη διακοπή μίας διεργασίας λόγω κάποιου λάθους ακόμα και αν γίνει μέσα στο κρίσιμο τμήμα συνήθως δεν έχεικαταστροφικά αποτελέσματα Το ΛΣ μέσω εξέτασης των δομώντων σημαφόρων που εμπλέκονται σε ένα σενάριο ταυτοχρονισμού έχει συνολική εικόνα της κατάστασης και μπορεί να αντιδράσει (είτετερματίζοντας όλες τις διεργασίες που εμπλέκονται στο σενάριο είτεεπιτρέποντας σε κάποιες από αυτές να συνεχίσουν την εκτέλεσήτους)
bull Οι τεχνικές υλοποίησης του αμοιβαίου αποκλεισμού είναι εύκολαεπεκτάσιμες σε σενάρια που εμπλέκουν πολλαπλές διεργασίες
bull Ο κώδικας συνολικά είναι πιο απλός και κατανοητός
71
Μειονεκτήματα των σημαφόρωνbull Ο προγραμματιστής συνεχίζει να είναιυπεύθυνος και για την υλοποίηση του αμοιβαίουαποκλεισμού και για τον συγχρονισμό μεταξύτων διεργασιών
bull Ειδικά σε πολύπλοκα σενάρια ταυτοχρονισμούόπου γίνεται χρήση πολλών σημαφόρων μπορεί εύκολα να δημιουργηθούν λάθη στοζευγάρωμα των εντολών semWait καιsemSignal προκαλώντας έτσι αδιέξοδο παρατεταμένη στέρηση ή μη αποτελεσματικήυλοποίηση του αμοιβαίου αποκλεισμού (δες καιπαραδείγματα αργότερα)
7272
Μηνύματα
bull Η διαδραστική σχέση μεταξύ των διεργασιών οδηγείστην ανάγκη υποστήριξηςndash Συγχρονισμού (μεταξύ άλλων και για υλοποίηση αμοιβαίουαποκλεισμού)
ndash Επικοινωνίας (για ανταλλαγή πληροφοριών μεταξύσυνεργαζόμενων διεργασιών)
bull Η δεύτερη ανάγκη υποστηρίζεται με τη χρήσημηνυμάτων (messages)
bull Ο μηχανισμός των μηνυμάτων είναι αρκετά ευέλικτος γιανα μπορεί να υλοποιηθεί σε οποιοδήποτε σύστημα μεκοινή ή κατανεμημένη μνήμη και με έναν ήπερισσότερους επεξεργαστές
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
7373
Ανταλλαγή μηνυμάτων
bull Αν και υπάρχουν πολλοί επί μέρους μηχανισμοίγια ανταλλαγή μηνυμάτων μεταξύ διεργασιών στη γενική περίπτωση υποστηρίζονται δύο είδηθεμελιωδών εντολώνndash send(διεύθυνση παραλήπτη μήνυμα) όπου ηδιεργασία που εκτελεί την εντολή αυτή στέλνει κάποιομήνυμα σε μια άλλη διεργασία (παραλήπτης)
ndash receive(διεύθυνση αποστολέα μήνυμα) όπου η διεργασία που εκτελεί την εντολή αυτήπαραλαμβάνει κάποιο μήνυμα από μια άλληδιεργασία (αποστολέας)
7474
Συγχρονισμός μεταξύ τωνδιεργασιών με χρήση μηνυμάτων
bull Η επικοινωνία μεταξύ των διεργασιών προϋποθέτει κάποιο είδοςσυγχρονισμού μεταξύ τουςndash Ο παραλήπτης δεν μπορεί να παραλάβει κάποιο μήνυμα αν δεν το
στείλει πρώτα ο αποστολέαςbull Όταν μία διεργασία εκτελέσει την εντολή send υπάρχουν δύο
πιθανότητεςndash Η διεργασία τίθεται υπό αναστολή μέχρις ότου το μήνυμα φτάσει στον
παραλήπτηndash Η διεργασία συνεχίζει κανονικά την εκτέλεσή της
bull Όταν μία διεργασία εκτελέσει την εντολή receive υπάρχουν καιπάλι δύο πιθανότητεςndash Αν το μήνυμα έχει ήδη φτάσει η διεργασία το χρησιμοποιεί και συνεχίζει
την εκτέλεση τηςndash Αν το μήνυμα δεν έχει φτάσει τότε η διεργασία
ndash Είτε τίθεται υπό αναστολή μέχρις ότου φτάσει το μήνυμαndash Είτε εγκαταλείπει την προσπάθειά της να πάρει το μήνυμα και συνεχίζει την
εκτέλεσή της
7575
Όταν και οι δύο εντολές θέτουνδιεργασίες υπό αναστολή
bull Σε αυτό το μοντέλο και ο αποστολέας και οπαραλήπτης τίθενται υπό αναστολή μέχριςότου το μήνυμα φτάσει στον παραλήπτη(blocking send blocking receive)
bull Γνωστό και ως ραντεβού (rendezvous)bull Επιτρέπει αυστηρό συγχρονισμό μεταξύτων διεργασιών
bull Χρησιμοποιήθηκε στη γλώσσαπρογραμματισμού Ada
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
7676
Όταν η εντολή send δεν θέτει τηνδιεργασία που την εκτέλεσε υπό
αναστολήbull Πιο φυσικός τρόπος επικοινωνίας μεταξύ τωνδιεργασιών (non blocking send blocking receive)
bull Ο αποστολέας συνεχίζει την εκτέλεση των εντολών μετάτην εκτέλεση της εντολής send χωρίς να περιμένει τομήνυμα να φτάσει στον παραλήπτη
bull Ο παραλήπτης όμως τίθεται υπό αναστολή μέχρις ότιπαραλάβει το μήνυμα
bull Υπάρχει και η παραλλαγή όπου ο παραλήπτης δενυποχρεούται να περιμένει την έλευση του μηνύματος(non blocking send non blocking receive)
7777
Καθορισμός διευθύνσεων σταμηνύματα
bull Υπάρχει ανάγκη να καθορισθεί ηδιεύθυνση του παραλήπτη σε μία εντολήτύπου send και του αποστολέα σε μίαεντολή τύπου receive
bull Υπάρχουν δύο τρόποι καθορισμού τηςδιεύθυνσης ενός αποστολέα ήπαραλήπτηndash Άμεσηndash Έμμεση
7878
Άμεσος καθορισμός διεύθυνσης
bull Η εντολή send περιλαμβάνει τη διεύθυνσημνήμης μίας συγκεκριμένης διεργασίαςπου είναι ο παραλήπτης του μηνύματος
bull Αντίστοιχα η εντολή receive είτεκαθορίζει και αυτή τη διεύθυνση μνήμηςενός συγκεκριμένου αποστολέα είτε αν οαποστολέας δεν είναι γνωστός η σχετικήπαράμετρος παίρνει τιμή με την παραλαβήτου μηνύματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
7979
Έμμεσος καθορισμός διεύθυνσης
bull Τα μηνύματα στέλνονται σε μία κοινή δομήδεδομένων που αποτελείται από ουρές
bull Αυτές οι ουρές ονομάζονταιγραμματοκιβώτια (mailboxes)
bull Ο αποστολέας στέλνει το μήνυμα σε έναγραμματοκιβώτιο και ο παραλήπτηςπαίρνει το μήνυμα από αυτό τογραμματοκιβώτιο
8080
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 1
81
Έμμεση επικοινωνία μεταξύ τωνδιεργασιών mdash 2
bull Σενάριο (α) σχέση μία-προς-μία επιτρέπει τηδημιουργία ενός προσωπικού καναλιού επικοινωνίαςμεταξύ δύο διεργασιών χωρίς να είναι δυνατή ηπαρεμβολή άλλων
bull Σενάριο (β) σχέση πολλές-προς-μία εδώ μία διεργασίαπροσφέρει υπηρεσίες σε ένα αριθμό από άλλεςδιεργασίες Το μοντέλο αυτό είναι χρήσιμο σεπεριβάλλοντα εξυπηρετητή-πελάτη (client-server) και σεαυτήν την περίπτωση το γραμματοκιβώτιο συνήθωςλέγεται θύρα (port)
bull Σενάριο (γ) σχέση μία-προς-πολλές επιτρέπει τηνεκπομπή πληροφοριών από μία διεργασία σε άλλες
bull Σενάριο (δ) σχέση πολλές-προς-πολλές επιτρέπει σεπολλαπλές εξυπηρετητές διεργασίες να επικοινωνούν μεπολλαπλές πελάτες διεργασίες για να προσφέρουνταυτόχρονες υπηρεσίες
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
8282
Γενική δομή ενός μηνύματος
8383
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση μηνυμάτωνconst int n = N number of processes
void P(int i)message msgwhile (1) preceding code receive (box msg) receive is blocking critical section send (box msg) send is non blocking remaining code
void main()create_mailbox (box)send (box null)parbegin P(0) P(1) hellip P(n) parend
8484
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματος
bull Σε επίπεδο γλωσσώνπρογραμματισμού
ndash Κλασσικά προβλήματα ταυτοχρονισμού
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
8585
Παρακολουθητής
bull Ο παρακολουθητής (monitor) είναι μία δομή πουπαρέχει λειτουργικότητα ισοδύναμη με αυτή τουσημαφόρου αλλά είναι πιο εύκολη η χρήση του
bull Προτάθηκε από τους Tony Hoare και Brinch Hansen το 1974-75
bull Μερικές από τις γλώσσες προγραμματισμούπου υποστηρίζουν παρακολουθητές είναι οιεξήςndash Concurrent Pascal Pascal-Plusndash Modula-2 Modula-3 και Java
8686
Βασικά χαρακτηριστικά τουπαρακολουθητή
bull Οι τοπικές δομές δεδομένων και μεταβλητές μέσα σεέναν παρακολουθητή είναι προσβάσιμες μόνο από τιςσυναρτήσεις του παρακολουθητή και όχι από εξωτερικέςσυναρτήσεις
bull Μία διεργασία εισέρχεται σε έναν παρακολουθητή μόνομέσω της κλήσης κάποιας από τις συναρτήσεις τουπαρακολουθητή (καθιστώντας έτσι τον παρακολουθητήπροπομπό του αντικειμενοστρεφούς προγραμματισμού)
bull Μόνο μία διεργασία ανά πάσα χρονική στιγμή μπορεί ναβρίσκεται μέσα σε έναν παρακολουθητή˙ οποιαδήποτεάλλη διεργασία που προσπαθεί να εισέλθει στονπαρακολουθητή τίθεται υπό αναστολή μέχρις ότου αυτόςείναι και πάλι διαθέσιμος
8787
Συγχρονισμός στη χρήση ενόςπαρακολουθητή
bull Επιτυγχάνεται με τη χρήση μεταβλητών συνθήκης (condition variables) πουβρίσκονται μέσα στον παρακολουθητή και είναι προσβάσιμες μόνο απόαυτόν
bull Η χρήση τους γίνεται μέσω δυο εντολώνndash cwait(c) θέτει υπό αναστολή τη διεργασία που την εκτελεί στην ουρά της
μεταβλητής συνθήκης c (ο παρακολουθητής είναι τώρα διαθέσιμος στιςυπόλοιπες διεργασίες)
ndash csignal(c) ενεργοποιεί τυχαία κάποια από τις διεργασίες που βρίσκονται υπόαναστολή στην ουρά της μεταβλητής συνθήκης c (αν η ουρά είναι άδεια τότε ηεκτέλεση της εντολής είναι σαν να μην έγινε)˙ ο παρακολουθητής τώραχρησιμοποιείται (αποκλειστικά) από τη διεργασία που ενεργοποιήθηκε
bull Σημειωτέον ότι η εντολή csignal λειτουργεί διαφορετικά από την εντολήsemSignal ενός (γενικού) σημαφόρου Στη δεύτερη περίπτωση ακόμα καιαν δεν υπάρχει καμία διεργασία υπό αναστολή αναφορικά με κάποιονσημαφόρο εκτελώντας ένα semSignal για αυτόν οδηγεί στην αύξηση τηςτιμής του κατά μία μονάδα Στην πρώτη περίπτωση η εκτέλεση τηςcsignal για κάποια μεταβλητή συνθήκης απλά αγνοείται αν δεν υπάρχεικαμία διεργασία υπό αναστολή αναφορικά με τη συνθήκη αυτή
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
8888
Δομή ενός παρακολουθητή
89
Επεξήγηση της δομής ενόςπαρακολουθητή
bull Αν και η πρόσβαση στον παρακολουθητή από μία διεργασία γίνεταιμέσω της κλήσης οποιασδήποτε συνάρτησης του παρακολουθητή μπορούμε να φανταστούμε ότι υπάρχει ένα μόνο σημείο πρόσβασηςαπό το οποίο εισέρχονται οι διεργασίες στον παρακολουθητή
bull Μόνο μία διεργασία ανά πάσα στιγμή μπορεί να είναι μέσα στονπαρακολουθητή και οι υπόλοιπες περιμένουν από έξω σε μία ουρά
bull Η διεργασία που βρίσκεται μέσα στον παρακολουθητή μπορεί ναθέσει τον εαυτό της υπό αναστολή στην ουρά κάποιας μεταβλητήςσυνθήκης x με την εκτέλεση της εντολής cwait(x) και θαπαραμείνει εκεί μέχρις ότου κάποια άλλη διεργασία τηνενεργοποιήσει μέσω της εντολής csignal(x) οπότε και θασυνεχίσει την εκτέλεσή της με τις εντολές που βρίσκονται μετά τηνεντολή cwait(x)
bull Αν η διεργασία που βρίσκεται στον παρακολουθητή διαπιστώσεικάποια αλλαγή στην κατάσταση της συνθήκης x εκτελεί την εντολήcsignal(x) οπότε και ενεργοποιεί κάποια από τις διεργασίες (ανυπάρχουν) που βρίσκονται υπό αναστολή στην ουρά της x
90
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητήmonitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()if (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecsignal(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
91
Πλεονεκτήματα τωνπαρακολουθητών έναντι των
σημαφόρωνbull Η ίδια η φύση του παρακολουθητή επιβάλει τηνεφαρμογή του αμοιβαίου αποκλεισμού Οπρογραμματιστής είναι υπεύθυνος μόνο για τονσυγχρονισμό των διεργασιών
bull Αυτός ο συγχρονισμός επιτυγχάνεται με τη χρήση τωνcwait και csignal που πρέπει να γίνει σωστά Όμως όταν ελεγχτεί και απασφαλματωθεί ο κώδικας τουπαρακολουθητή μπορεί μετά να χρησιμοποιηθεί απότον οποιοδήποτε όπως γίνεται με κάποια συνάρτησηβιβλιοθήκης
bull Κατrsquo επέκταση όταν ο κώδικας του παρακολουθητή δενέχει λάθη είναι εγγυημένο ότι οι διεργασίες επίσης δενέχουν λάθη (σε σχέση με την υλοποίηση του αμοιβαίουαποκλεισμού) Στην περίπτωση των σημαφόρων πρέπεινα ελέγχονται όλες οι διεργασίες
92
Μειονεκτήματα του παρακολουθητήbull O ορισμός του παρακολουθητή όπως διαμορφώθηκε από τους
Hoare και Hansen επιβάλλει ότι όταν εκτελείται μία εντολήcsignal(c) θα πρέπει αμέσως να ενεργοποιηθεί και να αρχίσειεκτέλεση κάποια από τις διεργασίες που είναι υπό αναστολή στησυνθήκη c
bull Επομένως η διεργασία που εκτέλεσε τη csignal θα πρέπει ή νατερματίσει αμέσως την εκτέλεσή της ή να την αναστείλει Αυτόδημιουργεί ορισμένα προβλήματαndash Αν η διεργασία που εκτέλεσε τη csignal δεν έχει ολοκληρώσει την
εκτέλεσή της τότε θα χρειασθούν δύο επιπλέον εναλλαγέςπεριβάλλοντος για αυτήν (αναστολή και επανεργοποίηση) Εναλλακτικά θα πρέπει να απαγορευθεί συντακτικά να υπάρχουν άλλες εντολές στιςσυναρτήσεις μετά από μία csignal που ουσιαστικά θα πρέπει να είναιη τελευταία εντολή της συνάρτησης (αυτός ο περιορισμός υιοθετήθηκεστη γλώσσα Concurrent Pascal)
ndash O μηχανισμός εναλλαγής στην KME της διεργασίας που εκτέλεσε τηνcsignal(c) με κάποια άλλη διεργασία υπό αναστολή στη c πρέπεινα γίνει με ακρίβεια διαφορετικά κάποια τρίτη διεργασία μπορεί ναπρολάβει να εκτελεσθεί πρώτη και να αλλάξει την c
93
Εναλλακτικός ορισμός τουπαρακολουθητή
bull Oι Lampson και Redell πρότειναν ένα διαφορετικό ορισμό τουπαρακολουθητή όπου αντί για τη csignal υπάρχει η εντολήcnotify
bull Ο ορισμός της cnotify είναι ο εξής η εκτέλεση της εντολήςcnotify(c) ναι μεν μεταφέρει το μήνυμα στην ουρά των υπόαναστολή διεργασιών στη συνθήκη c ότι κάποια από αυτές θαπρέπει κάποια στιγμή να ενεργοποιηθεί αλλά συνεχίζει τηνεκτέλεση της διεργασίας που έκανε χρήση της cnotify H διεργασία που πρέπει να ενεργοποιηθεί θα επαναρχίσει εκτέλεση σεκάποια κατάλληλη για το ΛΣ μελλοντική στιγμή
bull Έτσι επιτυγχάνονται τα εξήςndash Τα προηγούμενα μειονεκτήματα εξαλείφονται αφού δεν χρειάζονται οι δύο
επιπλέον εναλλαγές περιβάλλοντος για τη διεργασία που εκτέλεσε τη cnotifyκαι επιπλέον το ΛΣ δεν είναι υποχρεωμένο να υποστηρίξει αυστηρή εναλλαγήτων εμπλεκόμενων διεργασιών
ndash H μέθοδος αυτή τείνει να δημιουργεί λιγότερα λάθη γιατί δεν μπορεί οπρογραμματιστής να βασισθεί σε υποστήριξη αυστηρής εναλλαγής και είναιυποχρεωμένος να εξετάζει πιο συχνά τις συνθήκες αναστολής των διεργασιών
ndash Επιπλέον οδηγεί στη δημιουργία πιο αρθρωτών (modular) προγραμμάτων
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
94
Υλοποίηση του αμοιβαίουαποκλεισμού με χρήση
παρακολουθητή (με cnotify)monitor mutual_exclusion()boolean in_use = falsecond avail
void enter_critical()while (in_use)
cwait(avail)in_use = true
void exit_critical()in_use = falsecnotify(avail)
const int n = N
void P(int i)while (1) preceding code mutual_exclusionenter_critical() critical section mutual_exclusionexit_critical() following code
void main()parbegin
P(0) P(1) hellip P(n) parend
9595
Περιεχόμενα
ndash Ταυτοχρονισμός συντρέχωνπρογραμματισμός και η έννοια του αμοιβαίουαποκλεισμού
ndash Υλοποίηση αμοιβαίου αποκλεισμούbull Σε επίπεδο λογισμικούbull Σε επίπεδο υλικούbull Σε επίπεδο λειτουργικού συστήματοςbull Σε επίπεδο γλωσσών προγραμματισμού
ndashΚλασσικά προβλήματα ταυτοχρονισμού
9696
Το πρόβλημα του παραγωγού-καταναλωτή
bull Το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer)είναι ένα από τα κλασσικότερα προβλήματα ταυτοχρονισμού
bull Ένας ή περισσότεροι παραγωγοί παράγουν δεδομένα και τααποθηκεύουν σε μία προσωρινή μνήμη (buffer)
bull Ένας καταναλωτής αφαιρεί τα δεδομένα από τη μνήμη ένα κάθεφορά
bull Μόνο ένας παραγωγός ή καταναλωτής μπορεί να έχει πρόσβασηστη μνήμη ανά πάσα στιγμή
bull Το πρόβλημα εδώ είναι να αποφευχθούν οι περιπτώσεις ναπροσπαθήσει ένας παραγωγός να αποθηκεύσει δεδομένα στημνήμη όταν αυτή είναι γεμάτη ή να προσπαθήσει ο καταναλωτής νααφαιρέσει δεδομένα αν η μνήμη είναι άδεια
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
9797
Βασικός κώδικας
Παραγωγός Καταναλωτήςwhile (1) produce item v b[in] = vin++
while (1)while (in lt= out)
do nothing w = b[out]out++ consume item w
bull Θεωρούμε ένα προσωρινό χώρο μνήμης b μεάπειρες θέσεις μνήμης
9898
Η δομή της κοινής προσωρινήςμνήμης
9999
Λάθος επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1
delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)
buffer was empty so semSignalB(s) activate consumer
void consumer()semWaitB(delay) wait until buffer not empty while (1)
semWaitB(s)take()n--semSignalB(s)consume()if (n == 0) semWaitB(delay) buffer is empty
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
100
Επεξήγηση της λύσης
bull Η ιδέα της λύσης είναι η χρήση τηςμεταβλητής n (=in-out) για τηνυλοποίηση των δύο δεικτών in και out
bull Επίσης ο σημαφόρος s επιβάλει τοναμοιβαίο αποκλεισμό και ο σημαφόροςdelay αναγκάζει τον καταναλωτή ναπεριμένει αν η μνήμη είναι άδεια
101101
Πιθανή εκτέλεση τωνεμπλεκόμενων διεργασιών
102
Γιατί η λύση αυτή είναι λανθασμένηbull Όταν ο καταναλωτής έχει αφαιρέσει όλα τα δεδομένα από τη μνήμη
η εντολή if τον θέτει υπό αναστολήbull Στη γραμμή 14 όμως δεν ικανοποιείται η συνθήκη της if παρόλο
που ο καταναλωτής στη γραμμή 8 είχε μηδενίσει την τιμή τηςμεταβλητής n
bull Ο λόγος είναι ότι ο παραγωγός είχε αυξήσει την τιμή της n πριν οκαταναλωτής την εξετάσει στη γραμμή 14
bull Το αποτέλεσμα είναι η n να πάρει τιμή -1 που σημαίνει ότι οκαταναλωτής πήρε δεδομένα από άδεια μνήμη
bull Τα πρόβλημα δεν επιλύεται με τη μεταφορά της εντολή if μέσα στοκρίσιμο τμήμα του καταναλωτή (στην ομάδα των εντολών πουπερικλείονται από τις εντολές semWaitB(s) hellip semSignalB(s)) γιατί τότε θα είχαμε αδιέξοδο (πχ μετά την γραμμή 8)
bull Για την επίλυση του προβλήματος χρειάζεται η εισαγωγή μίαςτοπικής μεταβλητής στον καταναλωτή και να της δίνεται μέσα στοκρίσιμο τμήμα η τιμή της n
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
103103
Σωστή επίλυση του προβλήματοςμε δυαδικούς σημαφόρους
int nbinary_semaphore s = 1 delay = 0
void main()n = 0parbegin
producerconsumer
parend
void producer()while (1)
produce()semWaitB(s)append()n++if (n == 1) semSignalB(delay)semSignalB(s)
void consumer()int m local variable semWaitB(delay)while (1)
semWaitB(s)take()n--m = nsemSignalB(s)consume()if (m == 0) semWaitB(delay)
104104
Επίλυση του προβλήματος μεγενικούς σημαφόρους
semaphore n = 0 s = 1
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)consume()
105
Επεξήγηση της λύσηςbull Με χρήση γενικών σημαφόρων η λύση είναι κατά τι πιοαπλή
bull Η μεταβλητή n έχει αντικατασταθεί με ένα σημαφόροαλλά η τιμή της παραμένει ίση με το πλήθος τωνδεδομένων στη μνήμη
bull Ακόμα και αν κατά λάθος οι εντολές semSignal(s) καιsemSignal(n) εκτελεσθούν αντίστροφα (που σημαίνειότι η εντολή semSignal(n) θα εκτελεσθεί μέσα στοκρίσιμο τμήμα του παραγωγού χωρίς να διακοπεί είτεαπό κάποιον άλλο παραγωγό είτε από τον καταναλωτή) το πρόγραμμα θα εκτελεσθεί κανονικά διότι οκαταναλωτής πρέπει να εξετάσει και τους δύοσημαφόρους πριν εισέλθει στο κρίσιμο τμήμα του
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
106106
Παραλλαγή του προβλήματος μεμνήμη πεπερασμένων θέσεων
107107
Βασικός κώδικας με μνήμηπεπερασμένης χωρητικότητας
Παραγωγός Καταναλωτήςwhile (1) produce item v while ((in + 1) n == out)
do nothing b[in] = vin = (in + 1) n
while (1)while (in == out)
do nothing w = b[out]out = (out + 1) n consume item w
108108
Επίλυση του προβλήματος μεγενικούς σημαφόρους
const int sizeofbuffer = buffer size
semaphore s = 1 n = 0e = sizeofbuffer
void main()parbegin
producerconsumer
parend
void producer()while (1)
produce()semWait(e)semWait(s)append()semSignal(s)semSignal(n)
void consumer()while (1)
semWait(n)semWait(s)take()semSignal(s)semSignal(e)consume()
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
109109
Επίλυση του προβλήματος μεπαρακολουθητές mdash 1
monitor boundedbufferchar buffer[N] space for N items int nextin nextout buffer pointers int count number of items in buffer cond notfull notempty condition variables for synchronization
void append(char x)if (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer csignal(notempty) resume any waiting consumer
void take(char x)if (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer csignal(notfull) resume any waiting producer
monitor body nextin = 0 nextout = 0 count = 0 buffer initially empty
110110
Επίλυση του προβλήματος μεπαρακολουθητές mdash 2
void producer()char xwhile (1)
produce(x)boundedbufferappend(x)
void consumer()char xwhile (1)
boundedbuffertake(x)consume(x)
void main()parbegin producer consumer parend
111111
Παραλλαγή του κώδικα με χρήσηcnotify
void append(char x)while (count == N) cwait(notfull) buffer is full avoid overflow buffer[nextin] = xnextin = (nextin + 1) Ncount++ one more item in buffer cnotify(notempty) notify any waiting consumer
void take(char x)while (count == 0) cwait(notempty) buffer is empty avoid underflow x = buffer[nextout]nextout = (nextout + 1) Ncount-- one fewer item in buffer cnotify(notfull) notify any waiting producer bull Σημειώστε τη χρήση της εντολής while αντί της if Επειδή η cnotify δεν
εκτελείται αμέσως και κατrsquo επέκταση δεν υπάρχει εγγύηση ότι κάποια άλλη διεργασίαδεν θα προλάβει να εισέλθει στον παρακολουθητή πριν από αυτήν την διεργασία πουπεριμένει η τελευταία αυτή χρειάζεται να επανελέγξει αν συνεχίζει να ισχύει ησυνθήκη
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
112112
Επίλυση του προβλήματος μεμηνύματα
const int capacity = buffering capacity
null = empty message int i
void main()create_mailbox (mayproduce)create_mailbox (mayconsume)
for (int i = 1 i lt= capacity i++)send(mayproducenull)
parbeginproducerconsumer
parend
void producer() message pmsgwhile (1)
receive(mayproducepmsg)pmsg = produce()send(mayconsumepmsg)
void consumer()message cmsgwhile (1)
receive(mayconsumecmsg)consume(cmsg)send(mayproducenull)
113113
Το πρόβλημα των αναγνωστών-εγγραφέων
bull Tο πρόβλημα των αναγνωστών-εγγραφέων (readers-writers) μοντελοποιεί την πρόσβαση σε μία βάσηδεδομένων
bull Yπάρχει μία ομάδα από διεργασίες που προσπαθούν ναδιαβάσουν ταυτόχρονα μία περιοχή μνήμης και μίαομάδα από διεργασίες που προσπαθούν να γράψουνταυτόχρονα στην περιοχή αυτή
bull Mόνο μία διεργασία μπορεί ανά πάσα στιγμή να γράφειαλλά πολλές να διαβάζουν
bull Tην ώρα που μία διεργασία γράφει καμία άλλη δενμπορεί να διαβάζει
114114
Επίλυση του προβλήματος μεσημαφόρους (οι αναγνώστες έχουν
προτεραιότητα)int readcountsemaphore x = 1 wsem = 1
void main()readcount = 0parbegin
readerwriter
parend
void reader()while (1)
semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
void writer()while (1)
semWait(wsem)WRITEUNIT()semSignal(wsem)
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
115
Επεξήγηση και μειονεκτήματα τηςλύσης
bull Γίνεται χρήση δύο σημαφόρωνndash Ο wsem προστατεύει την κοινή περιοχή για γράψιμο ή διάβασμαndash Ο x προστατεύει την πρόσβαση στην κοινή μεταβλητήreadcount
bull Η λύση αυτή δίνει προτεραιότητα στους αναγνώστες μετην έννοια ότι αν έχει εισέλθει στην κοινή περιοχή έναςαναγνώστης τότε όσο θα έρχονται αναγνώστες δεν θαείναι δυνατόν σε εγγραφείς να χρησιμοποιήσουν τηνπεριοχή
bull Ο λόγος που αυτό είναι μειονέκτημα είναι γιατί συνήθωςσε τέτοια σενάρια έχουμε λίγους εγγραφείς και πολλούςαναγνώστες
bull Επομένως η λύση αυτή είναι δυνατόν να δημιουργήσειπαρατεταμένη στέρηση στους εγγραφείς
116116
Επίλυση του προβλήματος μεσημαφόρους (οι εγγραφείς έχουν
προτεραιότητα)int readcount writecountsemaphore x = 1 y = 1 z = 1
wsem = 1 rsem = 1
void writer()while (1)
semWait(y)writecount++if (writecount == 1)
semWait(rsem)semSignal(y)semWait(wsem)WRITEUNIT()semSignal(wsem)semWait(y)writecount--if (writecount == 0)
semSignal(rsem)semSignal(y)
void main()readcount = writecount = 0parbegin reader writer parend
void reader()while (1)
semWait(z)semWait(rsem)semWait(x)readcount++if (readcount == 1)
semWait(wsem)semSignal(x)semSignal(rsem)semSignal(z)READUNIT()semWait(x)readcount--if (readcount == 0)
semSignal(wsem)semSignal(x)
117117
Συμπεριφορά των σημαφόρων
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
118118
Επίλυση του προβλήματος μεμηνύματα mdash 1
void reader(int i)message rmsgwhile (1)
rmsg = isend(readrequestrmsg)receive(mbox[i]rmsg)READUNIT()rmsg = isend(finishedrmsg)
void writer(int j)message rmsgwhile (1)
rmsg = jsend(writerequestrmsg)receive(mbox[j]rmsg)WRITEUNIT()rmsg = jsend(finishedrmsg)
119119
Επίλυση του προβλήματος μεμηνύματα mdash 2
void controller()while (1)if (count gt 0)
if (empty(finished)) receive(finishedmsg)count++
else if (empty(writerequest))
receive(writerequestmsg)writer_id = msgidcount = count ndash 100
else if (empty(readrequest))
receive(readrequestmsg)count--send(msgidOK)
if (count == 0) send(writer_idOK)receive(finishedmsg)count = 100
while (count lt 0)
receive(finishedmsg)count++
120
Επεξήγηση της λύσης mdash 1bull Η κοινή μνήμη ελέγχεται από μία διεργασία
(controller)bull Αν ένας εγγραφέας ή αναγνώστης θέλει να έχειπρόσβαση στη μνήμη στέλνει μία αίτηση στονελεγκτή παίρνουν πίσω άδεια με ένα μήνυμα OKκαι όταν ολοκληρώσουν την πρόσβασηστέλνουν στον ελεγκτή το μήνυμα finished
bull Ο ελεγκτής έχει τρία γραμματοκιβώτια ένα γιακάθε κατηγορία μηνυμάτων που δύναται ναδεχθεί
bull Ο ελεγκτής δίνει προτεραιότητα στα μηνύματαπου προέρχονται από εγγραφείς
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
121
Επεξήγηση της λύσης mdash 2bull Η χρήση της μεταβλητής count με τον τρόπο πουδείχνει ο κώδικας υλοποιεί τον αμοιβαίο αποκλεισμό
bull Συγκεκριμένα στη count δίνεται ως αρχική τιμή έναςαριθμός μεγαλύτερος από τον μέγιστο αριθμόαναγνωστών (πχ 100) και ο controller λειτουργείως εξήςndash Αν countgt0 δεν υπάρχουν εγγραφείς σε αναμονή αλλά μπορείνα υπάρχουν αναγνώστες Εξυπηρέτησε πρώτα τα μηνύματαfinished και μετά αιτήσεις για γράψιμο και διάβασμα με αυτήτη σειρά
ndash Αν count=0 υπάρχει μόνο ένας εγγραφέας και εξυπηρετείταιndash Αν countlt0 υπάρχει ένας εγγραφέας που έχει κάνει αίτηση γιανα εισέλθει στο κρίσιμο τμήμα αλλά περιμένει λόγω ύπαρξηςαναγνωστών σε αυτό Σε αυτήν την περίπτωση δεν θα εισέλθειάλλος αναγνώστης στο κρίσιμο τμήμα και μόλις όλοι οιαναγνώστες σε αυτό έχουν εξέλθει θα εξυπηρετηθεί οεγγραφέας
122
Το πρόβλημα του κουρείου
123
Βασικά στοιχεία του προβλήματος
bull Μόνο 50 πελάτες μπορούν να βρίσκονται στο κουρείοανά πάσα στιγμή
bull Μόνο 4 πελάτες μπορούν να κάθονται στον καναπέbull Μόνο 3 πελάτες μπορούν να κουρεύονται ταυτόχροναbull Ο πελάτης πρώτα μπαίνει στην αίθουσα και στέκεταιόρθιος (αν ο καναπές είναι γεμάτος) μετά κάθεται στονκαναπέ και κάποια στιγμή κουρεύεται από κάποιον απότους κουρείς
bull Μετά το κούρεμα ο πελάτης πληρώνει στο μοναδικόταμείο που υπάρχει (ο κουρέας που τον εξυπηρέτησευιοθετεί προσωρινά το ρόλο του ταμία) πριν εξέλθει τουκουρείου
bull Αν κάποιος από τους κουρείς δεν έχει προσωρινάδουλειά τότε κοιμάται στην καρέκλα του
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
124
Λύση με σημαφόρους mdash 1semaphore max_capacity = 20
sofa = 4barber_chair = 3coord = 3cust_ready = 0finished = 0leave_b_chair = 0payment= 0receipt = 0
void main()parbegin
customer 50 times customerbarberbarberbarbercashier
parend
125
Λύση με σημαφόρους mdash 2void customer ()semWait(max_capacity)enter_shop()semWait(sofa)sit_on_sofa()semWait(barber_chair)get_up_from_sofa()semSignal(sofa)sit_in_barber_chair()semSignal(cust_ready)semWait(finished)leave_barber_chair()semSignal(leave_b_chair)pay()semSignal(payment)semWait(receipt)exit_shop()semSignal(max_capacity)
void barber()while (1)
semWait(cust_ready)semWait(coord)cut_hair()semSignal(coord)semSignal(finished)semWait(leave_b_chair)semSignal(barber_chair)
void cashier()while (1)
semWait(payment)semWait(coord)accept_pay()semSignal(coord)semSignal(receipt)
126
Ρόλος των σημαφόρων
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος
127
Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον
σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες
bull Αυτό σημαίνει ότι αν ένας κουρέας είναι πιο γρήγορος από τους άλλους ήένας πελάτης χρειάζεται λιγότερο κούρεμα θα δημιουργηθεί πρόβλημα μεκάποιον πελάτη είτε να σηκώνεται από την καρέκλα του πριν ολοκληρώσειτο κούρεμα είτε να περιμένει καθισμένος σε αυτή αν και έχει τελειώσει τοκούρεμα
bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει
bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά
bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο
bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος