Συγχρονισμός Διεργασιών
Γενικά Η ταυτόχρονη πρόσβαση από 2+ διεργασίες σε διαμοιραζόμενα δεδομένα μπορεί να προκαλέσει ασυνέπεια (inconsistency). Απαιτούνται μηχανισμοί που μπορούν να διασφαλίσουν την σωστή εκτέλεση των συνεργαζόμενων διεργασιών.
Bounded-Buffer Διαμοιραζόμενα Δεδομένα #define BUFFER_SIZE 10 typedef struct { . . . } item; item buffer[BUFFER_SIZE]; int in = 0; int out = 0; int counter = 0;
Bounded-Buffer Διεργασία Παραγωγού item nextProduced; while (1) { while (counter == BUFFER_SIZE) ; /* do nothing */ buffer[in] = nextProduced; in = (in + 1) % BUFFER_SIZE; counter++; }
Bounded-Buffer Διεργασία Καταναλωτή item nextConsumed; while (1) { while (counter == 0) ; /* do nothing */ nextConsumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; counter--; }
Bounded Buffer Οι εντολές counter++; counter--; Πρέπει να εκτελεστούν ατομικά. Ατομική λειτουργία: η λειτουργία εκτελείται στο σύνολό της χωρίς διακοπή.
Bounded Buffer Η εντολή “count++” μπορεί να υλοποιηθεί σε γλώσσα μηχανής ως εξής: register1 = counter register1 = register1 + 1 counter = register1 Η εντολή “count—” μπορεί να υλοποιηθεί ως εξής: register2 = counter register2 = register2 – 1 counter = register2
Bounded Buffer Εάν οι παραγωγός και καταναλωτής προσπαθήσουν να ενημερώσουν τον buffer ταυτόχρονα, οι εντολές της γλώσσας μηχανής μπορεί να διεμπλακούν (interleave). Η διεμπλοκή εξαρτάται από τον τρόπο χρονοπρογραμματισμού των διεργασιών παραγωγού και καταναλωτή.
Bounded Buffer Έστω ότι ο counter είναι αρχικά 5. Ενα πιθανό σενάριο διαμπλοκής είναι το ακόλουθο: παραγωγός: register1 = counter (register1 = 5) παραγωγός: register1 = register1 + 1 (register1 = 6) καταναλωτής: register2 = counter (register2 = 5) καταναλωτής: register2 = register2 – 1 (register2 = 4) παραγωγός: counter = register1 (counter = 6) καταναλωτής: counter = register2 (counter = 4) Η τιμή του count μπορεί να είναι 4 ή 6. Το σωστό αποτέλεσμα είναι το 5.
Συνθήκες Ανταγωνισμού Συνθήκη Ανταγωνισμού (Race condition): η κατάσταση όπου πολλαπλές διεργασίες – και διαχειρίζονται ταυτόχρονα διαμοιραζόμενα δεδομένα. Η τελική τιμή – κατάσταση των διαμοιραζόμενων δεδομένων εξαρτάται από το ποιά διεργασία ολοκληρώνει πρώτη. Για την αποφυγή συνθηκών ανταγωνισμού, οι συνδρομικές διεργασίες θα πρέπει να είναι συγχρονισμένες (synchronized).
Το πρόβλημα της κρίσιμης περιοχής n διεργασίες ανταγωνίζονται για την χρήση διαμοιραζόμενων δεδομένων Η κάθε διεργασία έχει ένα τμήμα κώδικα (code segment), το οποίο καλείται κρίσιμη περιοχή (critical section), στο πλαίσιο του οποίου επεξεργάζεται τα διαμοιραζόμενα δεδομένα. Πρόβλημα – θα πρέπει να διασφαλίσουμε ότι όταν μία διεργασία εκτελεί στην κρίσιμη περιοχή της, καμμία άλλη διεργασία δεν μπορεί να εκτελεί στην κρίσιμη περιοχή της.
Λύση στο πρόβλημα της κρίσιμης περιοχής 1. Αμοιβαίος Αποκλεισμός. Εάν η διεργασία Pi εκτελεί στην κρίσιμη περιοχή της, καμμία άλλη διεργασία δεν μπορεί να εκτελεί στην κρίσιμη περιοχή της. 2. Πρόοδος. Εάν καμμία διεργασία δεν εκτελεί στην κρίσιμη περιοχή της και υπάρχουν ορισμένες διεργασίες που θέλουν να εισέλθουν στην κρίσιμη περιοχή, τότε η επιλογή της διεργασίας που θα εισέλθει, επόμενη, στην κρίσιμη περιοχή δεν μπορεί να μετατεθεί επ’ αόριστον. 3. Περιορισμένη Αναμονή. Θα πρέπει να υπάρχει ένα όριο στον αριθμό των άλλων διεργασιών που μπορούν να εισέλθουν στην κρίσιμη περιοχή μετά την αίτηση μίας διεργασίας για είσοδο και πριν την ικανοποίηση αυτής της αίτησης. Θεωρούμε ότι η κάθε διεργασία εκτελεί με μη-μηδενική ταχύτητα Δεν γίνεται καμμία υπόθεση για την σχετική ταχύτητα των n διεργασιών.
Αρχικές Προσπάθειες Επίλυσης 2 διεργασίες, P0 and P1 Γενική δομή της διεργασίας Pi (η άλλη διεργασία Pj) do { entry section critical section exit section reminder section } while (1); Οι διεργασίες μπορούν να διαμοιράζονται κοινές μεταβλητές για να συγχρονήσουν τις ενέργειές τους.
Λύση 1 Διαμοιραζόμενες μεταβλητές: Διεργασία Pi do { int turn; αρχικά turn = 0 turn - i Pi μπορεί να εισέλθει στην κρίσιμη περιοχή Διεργασία Pi do { while (turn != i) ; critical section turn = j; remainder section } while (1); Ικανοποιεί τον περιορισμό του αμοιβαίου αποκλεισμού αλλά όχι της προόδου
Λύση 2 Διαμοιραζόμενες μεταβλητές Διεργασία Pi do { boolean flag[2]; αρχικά flag [0] = flag [1] = false. flag [i] = true η Pi είναι έτοιμη να εισέλθει στην κρίσιμη περιοχή Διεργασία Pi do { flag[ i ] := true; while (flag[ j ]) ; critical section flag[ i ] = false; remainder section } while (1); Ικανοποιεί τον αμοιβαίο αποκλεισμό αλλά όχι την πρόοδο.
Λύση 3 Συνδυασμός των διαμοιραζόμενων μεταβλητών των Λύσεων 1 και 2. Process Pi do { flag [i]:= true; turn = j; while (flag [j] and turn = j) ; critical section flag [i] = false; remainder section } while (1); Ικανοποιεί και τις τρείς απαιτήσεις; Επιλύει το πρόβλημα της κρίσιμης περιοχής για δύο διεργασίες.
Υποστήριξη Συγχρονισμού από το Υλικό Έλεγχος και μεταβολή του περιεχομένου μίας θέσης μνήμης ατομικά boolean TestAndSet(boolean &target) { boolean rv = target; target = true; return rv; }
Αμοιβαίος Αποκλεισμός σε Test-and-Set Διαμοιραζόμενα δεδομένα: boolean lock = false; Διεργασία Pi do { while (TestAndSet(lock)) ; critical section lock = false; remainder section }
Υποστήριξη Συγχρονισμού από το Hardware Ατομική εναλλαγή δύο μεταβλητών. void Swap(boolean &a, boolean &b) { boolean temp = a; a = b; b = temp; }
Αμοιβαίος Αποκλεισμός με Swap Διαμοιραζόμενα δεδομένα (αρχικοποιούνται σε false): boolean lock; boolean waiting[n]; Process Pi do { key = true; while (key == true) Swap(lock,key); critical section lock = false; remainder section }
Σημαφόροι Μέσο συγχρονισμού που δεν απαιτεί ενεργό αναμονή (busy waiting). Σημαφόρος S – ακέραια μεταβλητή Μπορούμε να επηρεάσουμε την τιμή του μόνο μέσω δύο αδιαίρετων (ατομικών) πράξεων wait (S): while S 0 do no-op; S--; signal (S): S++;
Κρίσιμη περιοχή n Διεργασιών Διαμοιραζόμενα δεδομένα: semaphore mutex; // αρχικά mutex = 1 Process Pi: do { wait(mutex); critical section signal(mutex); remainder section } while (1);
Υλοποίηση Σημαφόρου Ορισμός record typedef struct { int value; struct process *L; } semaphore; Υποθέτουμε δύο απλές λειτουργίες : block αναστέλλει την διεργασία που την καλεί. wakeup(P) αποκαθιστά την εκτέλεση της διεργασίας P που έχει ανασταλεί.
Υλοποίηση Οι λειτουργίες στον σημαφόρο ορίζονται ως εξής: wait(S): S.value--; if (S.value < 0) { πρόσθεσε τη διεργασία στο S.L; block; } signal(S): S.value++; if (S.value <= 0) { αφαίρεσε μία διεργασία P από το S.L; wakeup(P);
Ο Σημαφόρος ως γενικό μέσο συγχρονισμού Εκτέλεση του τμήματος B στην Pj μόνο μετά την εκτέλεση του Pi από την A Χρήση του σημαφόρου (αρχικοποίηση σε 0) Κώδικας: Pi Pj A wait(flag) signal(flag) B
Αδιέξοδο και Λιμοκτονία Αδιέξοδο – δύο ή περισσότερες διεργασίες περιμένουν επ’ αόριστον για ένα γεγονός το οποίο μπορεί να προκληθεί μόνο από άλλες διεργασίες που βρίσκονται επίσης σε αναμονή. Εστω οι σημαφόροι S και Q αρχικοποιημένοι 1 P0 P1 wait(S); wait(Q); wait(Q); wait(S); signal(S); signal(Q); signal(Q) signal(S); Λιμοκτονία – απροσδιόριστο blocking. Μία διεργασία μπορεί να μην αφαιρεθεί ποτέ από την ουρά του σημοφόρου όπου έχει ανασταλεί η εκτέλεσή της.
Δύο τύποι σημαφόρων Counting semaphore – ακέραια τιμή που μπορεί να λαμβάνει τιμές σε μη οριοθετημένο διάστημα. Binary semaphore – ακέραια τιμή που μπορεί να είναι 0 ή 1. Ένας counting semaphore S μπορεί να υλοποιηθεί σαν binary semaphore.
Υλοποίηση του S με δυαδικούς σημαφόρους Δομές δεδομένων: binary-semaphore S1, S2; int C: Αρχικοποίηση: S1 = 1 S2 = 0 C = αρχική τιμή του S
Υλοποίηση του S Λειτουργία wait wait(S1); C--; if (C < 0) { signal(S1); wait(S2); } Λειτουργία signal C ++; if (C <= 0) signal(S2); else
Προβλήματα Συγχρονισμού Πρόβλημα του Περιορισμένου Buffer (Bounded buffer) Πρόβλημα αναγνωστών συγγραφέων Πρόβλημα Dining-Philosophers
Πρόβλημα Bounded-Buffer Διαμοιραζόμενα δεδομένα semaphore full, empty, mutex; Αρχικά: full = 0, empty = n, mutex = 1
Bounded-Buffer: Διεργασία Παραγωγού do { … παραγωγή αντικειμένου στο nextp wait(empty); wait(mutex); προσθήκη του nextp στο buffer signal(mutex); signal(full); } while (1);
Bounded-Buffer: Διεργασία Καταναλωτή do { wait(full) wait(mutex); … αφαίρεση αντικειμένου από τον buffer στο nextc signal(mutex); signal(empty); κατανάλωση του αντικειμένου στο nextc } while (1);
Πρόβλημα Readers-Writers Διαμοιραζόμενα Δεδομένα semaphore mutex, wrt; αρχικά mutex = 1, wrt = 1, readcount = 0
Πρόβλημα Readers-Writers: Διεργασία Writer wait(wrt); … πραγματοποιείται η εγγραφή signal(wrt);
Readers-Writers Problem: Διεργασία Reader wait(mutex); readcount++; if (readcount == 1) wait(wrt); signal(mutex); … πραγματοποιείται η ανάγνωση readcount--; if (readcount == 0) signal(wrt); signal(mutex):
Πρόβλημα Dining-Philosophers Διαμοιραζόμενα δεδομένα semaphore chopstick[5]; Αρχικά όλες οι τιμές είναι 1
Πρόβλημα Dining-Philosophers Philosopher i: do { wait(chopstick[i]) wait(chopstick[(i+1) % 5]) … eat signal(chopstick[i]); signal(chopstick[(i+1) % 5]); think } while (1);