Προβλήματα Συγχρονισμού (με ελεγκτή) Ταυτόχρονος Προγραμματισμός 1 lalis@inf.uth.gr
Υλοποίηση σηματοφόρων Οι σηματοφόροι είναι ένας ΑΤΔ με συγκεκριμένες λειτουργίες πρόσβασης Μπορεί να υλοποιηθούν με «φυσικό» τρόπο χρησιμοποιώντας τον μηχανισμό του ελεγκτή Ο αμοιβαίος αποκλεισμός κατά την πρόσβαση του εσωτερικού μετρητή επιτυγχάνεται αυτόματα, λόγω του μηχανισμού του ελεγκτή Το πρόβλημα που πρέπει να λυθεί είναι η αναμονή μέσα στην down, και αφύπνιση μέσω της up Χρησιμοποιούμε μια μεταβλητή συνθήκης Ταυτόχρονος Προγραμματισμός 2 lalis@inf.uth.gr
monitor binsem { bit val; /* τιμή σηματοφόρου */ condition q; /* ουρά αναμονής */ void init(bit v) { val = v; void down() { if (val == 0) { wait(q); val = 0; void up() { val = 1; signal(q); Ταυτόχρονος Προγραμματισμός 3 lalis@inf.uth.gr
monitor binsem { bit val; /* τιμή σηματοφόρου */ condition q; /* ουρά αναμονής */ void init(bit v) { val = v; είναι σωστό για μοντέλο open; void down() { if (val == 0) { wait(q); val = 0; void up() { val = 1; signal(q); Ταυτόχρονος Προγραμματισμός 4 lalis@inf.uth.gr
monitor binsem { bit val; /* τιμή σηματοφόρου */ condition q; /* ουρά αναμονής */ void init(bit v) { val = v; είναι σωστό για μοντέλο open; void down() { while (val == 0) { wait(q); val = 0; void up() { val = 1; signal(q); Ταυτόχρονος Προγραμματισμός 5 lalis@inf.uth.gr
monitor binsem { int val; /* τιμή σηματοφόρου */ condition q; /* ουρά αναμονής */ void init(int v) { val = v; τεχνική «κοιμώμενου κουρέα» void down() { val--; if (val < 0) { wait(q); void up() { if (val < 1) { val++; if (val <= 0) { signal(q); Ταυτόχρονος Προγραμματισμός 6 lalis@inf.uth.gr
Παραγωγός-καταναλωτής Το κλασικό πρόβλημα του παραγωγού-καταναλωτή με αποθήκη απεριόριστης χωρητικότητας Ο καταναλωτής πρέπει να μπλοκάρεται όταν επιχειρεί να απομακρύνει ένα κομμάτι από άδεια αποθήκη Και να αφυπνίζεται όταν τοποθετηθούν νέα κομμάτια Υλοποιούμε την αποθήκη ως ΑΔΤ σε ελεγκτή Η αναμονή/αφύπνιση καταναλωτών επιτυγχάνεται (α) μετρώντας τον αριθμό των κομματιών στην αποθήκη, και (β) χρησιμοποιώντας μια μεταβλητή συνθήκης Ταυτόχρονος Προγραμματισμός 7 lalis@inf.uth.gr
monitor buf { void init(); void put(data d); Data get(); Producer: while (1) {... /* produce Data item d */ put(d); Consumer: while (1) {... d=get(); /* consume Data item d */...... Ταυτόχρονος Προγραμματισμός 8 lalis@inf.uth.gr
monitor buf {... /* μεταβλητές αποθήκης */ int avl; /* # γεμάτων θέσεων */ condition notempty; /* αναμονή καταναλωτή */ void init() {... /* αρχικοποίηση αποθήκης */ avl = 0; void put(data d) {... /* τοποθέτηση στοιχείου στην αποθήκη */ avl++; if (avl == 1) { signal(notempty); Data get() { if (avl == 0) { wait(notempty); avl--;... /* απομάκρυση στοιχείου από την αποθήκη */ return(...); Ταυτόχρονος Προγραμματισμός 9 lalis@inf.uth.gr
monitor buf {... /* μεταβλητές αποθήκης */ int avl; /* # γεμάτων θέσεων */ condition notempty; /* αναμονή καταναλωτή */ void init() {... /* αρχικοποίηση αποθήκης */ avl = 0; είναι σωστό για μοντέλο open; void put(data d) {... /* τοποθέτηση στοιχείου στην αποθήκη */ avl++; if (avl == 1) { signal(notempty); Data get() { if (avl == 0) { wait(notempty); avl--;... /* απομάκρυση στοιχείου από την αποθήκη */ return(...); Ταυτόχρονος Προγραμματισμός 10 lalis@inf.uth.gr
Πρόβλημα με μοντέλο open Ο καταναλωτής ελέγχει την αποθήκη και μπλοκάρει αν αυτή είναι άδεια μια μοναδική φορά Όμως, όταν καλείται η signal, ο έλεγχος δεν δίνεται απαραίτητα στον καταναλωτή που περίμενε Μπορεί να δοθεί σε έναν άλλο καταναλωτή, που έχει μπλοκάρει προσπαθώντας να καλέσει την get, και έτσι να προσπεραστεί ο πρώτος καταναλωτής Η συνθήκη αφύπνισης μπορεί να ακυρωθεί Η συνθήκη αναμονής πρέπει να ελέγχεται ξανά, αφού ο καταναλωτής εκτελέσει / βγει από την wait Ταυτόχρονος Προγραμματισμός 11 lalis@inf.uth.gr
monitor buf {... /* μεταβλητές αποθήκης */ int avl; /* # γεμάτων θέσεων */ condition notempty; /* αναμονή καταναλωτή */ void init() {... /* αρχικοποίηση αποθήκης */ avl = 0; είναι σωστό για μοντέλο open; void put(data *d) {... /* τοποθέτηση στοιχείου στην αποθήκη */ avl++; if (avl == 1) { signal(notempty); Data *get() { while (avl == 0) { wait(notempty); avl--;... /* απομάκρυνση στοιχείου από την αποθήκη */ return(...); Ταυτόχρονος Προγραμματισμός 12 lalis@inf.uth.gr
Πρόβλημα με μοντέλο open Δεν γνωρίζουμε πότε ακριβώς πρέπει να αφυπνιστεί ένας καταναλωτής μέσω signal(notempty) Η συνθήκη αφύπνισης δεν είναι απαραίτητα σωστή Γιατί; Ως συνέπεια, υπάρχει περίπτωση να μην κληθεί πότε signal(notempty) παρότι κάποιος καταναλωτής έχει μπλοκάρει και η αποθήκη δεν είναι άδεια Ταυτόχρονος Προγραμματισμός 13 lalis@inf.uth.gr
monitor buf {... /* μεταβλητές αποθήκης */ int avl; /* # γεμάτων θέσεων */ condition notempty; /* αναμονή καταναλωτή */ void init() {... /* αρχικοποίηση αποθήκης */ avl = 0; void put(data *d) {... /* τοποθέτηση στοιχείου στην αποθήκη */ avl++; signal(notempty); /* σε κάθε περίπτωση */ Data *get() { while (avl == 0) { wait(notempty); avl--;... /* απομάκρυνση στοιχείου από την αποθήκη */ return(...); Ταυτόχρονος Προγραμματισμός 14 lalis@inf.uth.gr
Πρόβλημα με μοντέλο open Υπάρχει ακόμα πρόβλημα λιμοκτονίας, καθώς ένας καταναλωτής που περιμένει μπορεί να προσπερνιέται συνεχώς από νέους καταναλωτές Λύση: τεχνική κοιμώμενου κουρέα Ταυτόχρονος Προγραμματισμός 15 lalis@inf.uth.gr
monitor buf {... /* μεταβλητές αποθήκης */ int avl; /* # γεμάτων θέσεων */ condition notempty; /* αναμονή καταναλωτή */ void init() {... /* αρχικοποίηση αποθήκης */ avl = 0; void put(data *d) {... /* τοποθέτηση στοιχείου στην αποθήκη */ avl++; if (avl <= 0) { signal(notempty); Data *get() { avl--; if (avl < 0) { wait(notempty);... /* απομάκρυνση στοιχείου από την αποθήκη */ return(...); Ταυτόχρονος Προγραμματισμός 16 lalis@inf.uth.gr
Παραγωγός-καταναλωτής με αποθήκη περιορισμένης χωρητικότητας Η σχέση αναμονής/αφύπνισης είναι συμμετρική ανάμεσα στους καταναλωτές και τους παραγωγούς Ότι κάναμε για την αναμονή των καταναλωτών (σε άδεια αποθήκη) και την αφύπνιση τους από τους παραγωγούς, το ίδιο πρέπει να γίνει και για την αναμονή παραγωγών (σε γεμάτη αποθήκη) και την αφύπνιση τους από τους καταναλωτές Στο ίδιο πνεύμα με την προηγούμενη λύση Ταυτόχρονος Προγραμματισμός 17 lalis@inf.uth.gr
monitor buf {... /* μεταβλητές αποθήκης */ int free,full; /* # άδειων/γεμάτων θέσεων */ condition notfull,notempty; /* αναμονή παρ. & κατ. */ void init() {... /* αρχικοποίηση αποθήκης */ free = N; full = 0; void put(data *d) { if (free == 0) { wait(notfull); free--;... /* τοποθέτηση στοιχείου στην αποθήκη */ full++; if (full == 1) { signal(notempty); Data *get() { Data *d; if (full == 0) { wait(notempty); full--;... /* απομάκρυση στοιχείου από την αποθήκη */ free++; if (free == 1) { signal(notfull); return(...); Ταυτόχρονος Προγραμματισμός 18 lalis@inf.uth.gr
monitor buf {... /* μεταβλητές αποθήκης */ int free,full; /* # άδειων/γεμάτων θέσεων */ condition notfull,notempty; /* αναμονή παρ. & κατ. */ void init() {... /* αρχικοποίηση αποθήκης */ free = N; full = 0; void put(data *d) { free--; if (free < 0) { wait(notfull);... /* τοποθέτηση στοιχείου στην αποθήκη */ full++; if (full <= 0) { signal(notempty); Data *get() { Data *d; full--; if (full < 0) { wait(notempty);... /* απομάκρυση στοιχείου από την αποθήκη */ free++; if (free <= 0) { signal(notfull); return(...); Ταυτόχρονος Προγραμματισμός 19 lalis@inf.uth.gr
Αναγνώστες και εγγραφείς ιδέα Κώδικας εισόδου/εξόδου μέσα σε ελεγκτή, με ξεχωριστές ουρές αναμονής (μεταβλητές συνθήκης) Είσοδος αναγνώστη: αν υπάρχει στο ΚΤ εγγραφέας, περίμενε στην ουρά των αναγνωστών Είσοδος εγγραφέα: αν υπάρχει στο ΚΤ αναγνώστης ή εγγραφέας, περίμενε στην ουρά των εγγραφέων Έξοδος τελευταίου αναγνώστη: ξύπνα τον επόμενο εγγραφέα, αν υπάρχει Έξοδος εγγραφέα: ξύπνα τον/τους επόμενο/ους αναγνώστη/ες, αν υπάρχουν, διαφορετικά τον επόμενο εγγραφέα, αν υπάρχει Ταυτόχρονος Προγραμματισμός 20 lalis@inf.uth.gr
monitor rwsync { void init(); void startread(); void stopread(); void startwrite(); void stopwrite(); Reader: while (1) { startread(); /* read */ stopread(); Writer: while (1) { startwrite(); /* write */ stopwrite(); Ταυτόχρονος Προγραμματισμός 21 lalis@inf.uth.gr
monitor rwsync { int rds,wrs; /* #αναγνωστών/εγγραφέων στο ΚΤ */ condition rq,wq; /* ουρές αναμονής */ int rcnt, wcnt; /* #νημάτων μπλοκαρισμένα σε rq/wq */ void init() { rds = 0; wrs = 0; rcnt = 0; wcnt =0; void startread(); void stopread(); void startwrite(); void stopwrite(); Ταυτόχρονος Προγραμματισμός 22 lalis@inf.uth.gr
void startread() { if (wrs > 0) { rcnt++; wait(rq); rds++; void stopread() { rds--; if ((rds == 0) && (wcnt > 0)) { wcnt--; signal(wq); void startwrite() { if ((wrs > 0) (rds > 0)) { wcnt++; wait(wq); wrs++; void stopwrite() { wrs--; if (rcnt > 0) { do { rcnt--; signal(rq); while (rcnt > 0); else if (wcnt > 0) { wcnt--; signal(wq); Ταυτόχρονος Προγραμματισμός 23 lalis@inf.uth.gr
void startread() { if (wrs > 0) { rcnt++; wait(rq); if (rcnt > 0) { rcnt--; signal(rq); rds++; void stopread() { rds--; if ((rds == 0) && (wcnt > 0)) { wcnt--; signal(wq); void startwrite() { if ((wrs > 0) (rds > 0)) { wcnt++; wait(wq); wrs++; void stopwrite() { wrs--; if (rcnt > 0) { rcnt--; signal(rq); else if (wcnt > 0) { wcnt--; signal(wq); Ταυτόχρονος Προγραμματισμός 24 lalis@inf.uth.gr
Λιμοκτονία εγγραφέων Ένας νέος αναγνώστης θα προσπεράσει έναν εγγραφέα που περιμένει, αν στο ΚΤ υπάρχουν ήδη και άλλοι αναγνώστες Αυτό μπορεί να συνεχίζεται απ άπειρο Πιο δίκαιο: ένας νέος αναγνώστης πρέπει να περιμένει αν ήδη περιμένει ένας εγγραφέας Με ελεγκτή (σε αντίθεση με τους σηματοφόρους), αυτό είναι εύκολο να επιτευχθεί, με μια απλή επέκταση της υπάρχουσας λύσης Ταυτόχρονος Προγραμματισμός 25 lalis@inf.uth.gr
void startread() { if ((wrs > 0) (wcnt > 0)) { rcnt++; wait(rq); if (rcnt > 0) { rcnt--; signal(rq); rds++; void stopread() { rds--; if ((rds == 0) && (wcnt > 0)) { wcnt--; signal(wq); void startwrite() { if ((wrs > 0) (rds > 0)) { wcnt++; wait(wq); wrs++; void stopwrite() { wrs--; if (rcnt > 0) { rcnt--; signal(rq); else if (wcnt > 0) { wcnt--; signal(wq); Ταυτόχρονος Προγραμματισμός 26 lalis@inf.uth.gr
Προσπεράσματα Πάλι μπορεί να προσπεραστεί ένας εγγραφέας Όταν εξέρχεται ένας εγγραφέας από το ΚΤ, δίνει προτεραιότητα στους αναγνώστες, που με την σειρά τους αφυπνίζονται όλοι Όμως, δεν μπορεί να υπάρξει λιμοκτονία Γιατί; Ανάποδα, η προτεραιότητα εισόδου μπορεί να δοθεί στους εγγραφείς τότε όμως πάλι υπάρχει περίπτωση λιμοκτονίας για τους αναγνώστες Ταυτόχρονος Προγραμματισμός 27 lalis@inf.uth.gr
Συνδαιτημόνες φιλόσοφοι ιδέα Υλοποιούμε τη ρουτίνα getforks(i) μέσω της οποίας ο i-οστός φιλόσοφος επιχειρεί να πιάσει τα πιρούνια που βρίσκονται δεξιά και αριστερά του Υλοποιούμε τη ρουτίνα putforks(i) μέσω της οποίας ο i-οστός φιλόσοφος αφήνει τα πιρούνια του Χρησιμοποιούμε ένα πίνακα avl[ν] που καταγράφει τα διαθέσιμα πιρούνια για κάθε φιλόσοφο (0, 1, 2) Χρησιμοποιούμε ένα πίνακα caneat[ν] από μεταβλητές συνθήκης, μια για κάθε φιλόσοφο Ταυτόχρονος Προγραμματισμός 28 lalis@inf.uth.gr
monitor table { Pi: while (1) { /* think */ void init(); void getforks(int i); void putforks (int i); getforks(i); /* eat */ putforks(i); Ταυτόχρονος Προγραμματισμός 29 lalis@inf.uth.gr
monitor dining_philosophers { int avl[n]; condition caneat[ν]; void init() { int i; for (i=0; i<ν; i++) { avl[i] = 2; void getforks(int i) { if (avl[i]!= 2) { wait(caneat[i]); avl[right(i)]--; avl[left(i)]--; void putforks(int i) { avl[right(i)]++; if (avl[right(i)] == 2) { signal(caneat[right(i)]); avl[left(i)]++; if (avl[left(i)] == 2) { signal(caneat[left(i)]); Ταυτόχρονος Προγραμματισμός 30 lalis@inf.uth.gr
Παρατηρήσεις Ο αμοιβαίος αποκλεισμός είναι εγγυημένος Δεν υπάρχει περίπτωση αδιεξόδου, και πάντα κάποιος φιλόσοφος θα καταφέρνει να τρώει Υπάρχει όμως περίπτωση λιμοκτονίας Ένας φιλόσοφος δεν θα καταφέρει να φάει ποτέ, αν οι γείτονες του τρώνε συντονισμένα εναλλάξ έτσι ώστε ποτέ να μην είναι ταυτόχρονα διαθέσιμα τόσο το δεξί όσο και το αριστερό πιρούνι του Υπάρχει θέμα signal-block ή signal-continue; Είναι η λύση σωστή για μοντέλο open; Ταυτόχρονος Προγραμματισμός 31 lalis@inf.uth.gr
Υλοποίηση ελεγκτή με σηματοφόρους (eggshell/signal-block) Ταυτόχρονος Προγραμματισμός 32 lalis@inf.uth.gr
Πρόβλημα Έστω ότι μας δίνονται σηματοφόροι Μπορούμε να πετύχουμε λειτουργικότητα συγχρονισμού στο πνεύμα ενός ελεγκτή; Ζητούμενο 1 ο : Πως επιτυγχάνεται το πλαίσιο του αμοιβαίου αποκλεισμού; Ζητούμενο 2 ο : Πως υλοποιείται μια μεταβλητή συνθήκης, καθώς και οι λειτουργίες wait / signal; Ταυτόχρονος Προγραμματισμός 33 lalis@inf.uth.gr
Eggshell mtx ουρά εισόδου x_q ουρά νημάτων που έχουν καλέσει την wait στη μεταβλητή συνθήκης x s_q νήμα που εκτελεί κώδικα του ελεγκτή ουρά νημάτων που περιμένουν (λόγω κλήσης της signal) να ελευθερωθεί ο ελεγκτής Ταυτόχρονος Προγραμματισμός 34 lalis@inf.uth.gr
Μεταβλητές πλαισίου συγχρονισμού Σηματοφόρος για αμοιβαίο αποκλεισμό: bsem mtx; /* αμοιβαίος αποκλεισμός */ init(&mtx,1); Για κάθε μεταβλητή συνθήκης x, σηματοφόρος και μετρητής για την υλοποίηση της ουράς αναμονής: bsem x_q; /* ουρά μεταβλητής συνθήκης */ int x_n=0; /* # νημάτων στην ουρά */ init(&x_q,0); x_n = 0; Σηματοφόρος και μετρητής για τις διεργασίες που μπλοκάρουν λόγω κλήσης της signal: bsem s_q; /* ουρά για signal */ int s_n; /* # νημάτων στην ουρά */ init(&s_q,0); s_n = 0; Ταυτόχρονος Προγραμματισμός 35 lalis@inf.uth.gr
void proc( ) { down(mtx); /* συμβατικός κώδικας της ρουτίνας */ if (s_n > 0) { s_n--; up(s_q); else { up(mtx); void wait(condition x) { x_n++; if (s_n > 0) { s_n--; up(s_q); else { up(mtx); down(x_q); void signal(condition x) { if (x_n > 0) { s_n++; x_n--; up(x_q); down(s_q); Ταυτόχρονος Προγραμματισμός 36 lalis@inf.uth.gr
Μια λύση αποκλειστικά για signal&exit Έστω ότι η signal καλείται μόνο ως τελευταία εντολή μιας λειτουργίας του ελεγκτή (ή τερματίζει άμεσα την λειτουργία, στο πνεύμα της exit/return) Τότε δεν χρειάζεται να μπλοκάρει το νήμα που καλεί την signal δεν τίθεται θέμα αμοιβαίου αποκλεισμού αφού το νήμα στην συνέχεια θα εγκαταλείψει τον ελεγκτή Συνεπώς δεν χρειάζεται η ουρά που υλοποιείται μέσω του σηματοφόρου s_q και μετρητή s_n Ταυτόχρονος Προγραμματισμός 37 lalis@inf.uth.gr
void proc( ) { down(mtx); /* */ up(mtx); void wait(condition x) { x_n++; up(mtx); down(x_q); void signal(condition x) { if (x_n > 0) { x_n--; up(x_q); else { up(mtx); Ταυτόχρονος Προγραμματισμός 38 lalis@inf.uth.gr