43
Γιώργος Α. Παπαδόπουλος Τμήμα Πληροφορικής Πανεπιστήμιο Κύπρου ΕΠΛ222: Λειτουργικά Συστήματα (μετάφραση στα ελληνικά των διαφανειών του βιβλίου Operating Systems: Internals and Design Principles, 6/E, William Stallings) Ενότητα 4 Συντρέχων Προγραμματισμός Οι διαφάνειες αυτές έχουν συμπληρωματικό και επεξηγηματικό χαρακτήρα και σε καμία περίπτωση δεν υποκαθιστούν το βιβλίο 2 Περιεχόμενα Ταυτοχρονισμός, συντρέχων προγραμματισμός και η έννοια του αμοιβαίου αποκλεισμού. Υλοποίηση αμοιβαίου αποκλεισμού: Σε επίπεδο λογισμικού. Σε επίπεδο υλικού. Σε επίπεδο λειτουργικού συστήματος. Σε επίπεδο γλωσσών προγραμματισμού. Κλασσικά προβλήματα ταυτοχρονισμού. 3 Ταυτοχρονισμός και Παραλληλισμός Με τον όρο ταυτοχρονισμό (concurrency) αναφερόμαστε στην ταυτόχρονη εκτέλεση δύο ή περισσοτέρων διεργασιών σε ένα σύστημα. Αυτό επιτυγχάνεται με τη συνεχή εναλλαγή των εκτελούμενων διεργασιών στην KME. Σημειωτέον ότι ο ταυτοχρονισμός διαφέρει από τον παραλληλισμό (parallelism) όπου το σύστημα διαθέτει πολλούς επεξεργαστές οι οποίοι εκτελούν παράλληλα περισσότερες από μία διεργασίες.

Enothta-4

Embed Size (px)

Citation preview

Page 1: Enothta-4

Γιώργος Α ΠαπαδόπουλοςΤμήμα ΠληροφορικήςΠανεπιστήμιο Κύπρου

ΕΠΛ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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 2: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 3: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 4: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 5: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 6: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 7: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 8: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 9: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 10: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 11: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 12: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 13: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 14: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 15: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 16: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 17: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 18: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 19: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 20: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 21: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 22: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 23: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 24: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 25: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 26: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 27: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 28: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 29: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 30: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 31: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 32: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 33: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 34: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 35: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 36: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 37: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 38: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 39: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 40: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 41: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 42: Enothta-4

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 Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος

Page 43: Enothta-4

127

Προβλήματα με τη λύσηbull Ένα πρώτο πρόβλημα που προέρχεται από το συντονισμό με τον

σημαφόρο finished είναι ότι οι 3 πελάτες που κουρεύονται πρέπει ναολοκληρώσουν το κούρεμα με τη σειρά που κάθισαν στις καρέκλες

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

bull Επίσης αν περιμένουν για να πληρώσουν περισσότεροι από ένας πελάτες ο κουρέας που παίζει το ρόλο του ταμία μπορεί να πληρωθεί από ένανπελάτη αλλά να αφήσει άλλον να φύγει

bull Επιπλέον υπάρχουν περιπτώσεις που ο αμοιβαίος αποκλεισμός στις 3 καρέκλες δεν λειτουργεί σωστά

bull Τέλος ένας πελάτης είναι υποχρεωμένος πρώτα να καθίσει στον καναπέκαι μετά σε κάποια καρέκλα για να κουρευτεί ακόμα και αν το κουρείο είναιάδειο

bull Δείτε το παράρτημα Α για λεπτομερή ανάλυση του προβλήματος