Νήµατα Εκτέλεσης Συγχρονισµός ιεργασιών σε Κοινή Μνήµη
Για τη δηµιουργία των διαφανειών έχει χρησιµοποιηθεί υλικό από τις διαφάνειες παραδόσεων που βασίζονται στο βιβλίο, Silberschatz, Galvin and Gagne, Operating Systems Concepts, 6 th Edition. Οι διαφάνειες αυτές βρίσκονται στο δικτυακό τόπο: http://www.cs.purdue.edu/homes/yau/cs503/
Νήµατα Εκτέλεσης (Threads)
Εισαγωγή Επισκόπηση Μοντέλα πολλαπλών νηµάτων (Multithreading Models) Θέµατα νηµάτων (Threading Issues) Pthreads και νήµατα σε µερικά δηµοφιλή ΛΣ Σελίδα 4
Η Έννοια της ιεργασίας (και) Γιατίοι ιεργασίεςδενείναιαρκετές Η διεργασία είναι κυρίως µια λογική έννοια, η οποίαστηνπράξη υλοποιείται µε τονµηχανισµό της διεργασίας σε επίπεδο ΛΣ. Μια λογική διεργασία µπορεί να αντιστοιχεί σε µια (ή περισσότερες) διεργασίες του ΛΣ Τι γίνεται όµως αν µια λογική διεργασία χρειάζεται να υποστηρίξει «ταυτόχρονη» εκτέλεση κώδικα; Μπορεί να δηµιουργηθούν πολλές διεργασίες (ΛΣ) που συνεργάζονται για τον κοινό σκοπό (της λογικής διεργασίας) Όµως ακόµα και αν οι διεργασίες δηµιουργηθούν έτσι ώστε να επικοινωνούν (αποδοτικά) µέσω κοινής µνήµης, το σύστηµα επιβαρύνεται λόγω των επιπρόσθετων εναλλαγών περιβάλλοντος λειτουργίας (context switch) που πραγµατοποιούνται Σελίδα 5
Νήµατα Τα νήµατα (νήµατα ελέγχου), ονοµάζονται και ελαφρές διεργασίες (lightweight processes LWPs), είναι ξεχωριστές ροές του ίδιου προγράµµατος που εκτελούνται µέσα σε µια λογική διεργασία Τα νήµατα µιας διεργασίας ανήκουν πάντα στον ίδιο χρήστη Λειτουργούν (στην ουσία) όπως και οι διεργασίες, δεν είναι όµως πραγµατικά ανεξάρτητες εκτελέσεις, καθώς: έχουν τα ίδια δικαιώµατα πρόσβασης σε πόρους µοιράζονται τους ίδιους πόρους (δεδοµένα, κώδικα, ανοικτά αρχεία, σήµατα, ακόµα και τον χρόνο της ΚΜΕ) µοιράζονται την ίδια µνήµη! Σελίδα 6
Thread Switch vs Context Switch Για κάθε νήµα διατηρείται ξεχωριστή κατάσταση εκτέλεσης, δηλαδή µετρητής προγράµµατος, τιµές των καταχωρητών και στοίβα Η εναλλαγή µεταξύ νηµάτων (thread switch) ισοδυναµεί στην πράξη µε απλή εναλλαγή κατάστασης ΚΜΕ (register set switch) και όχι µε πραγµατική εναλλαγή περιβάλλοντος λειτουργίας (context switch) Κατά το thread switch δεν πραγµατοποιούνται λειτουργίες διαχείρισης µνήµης και δικαιωµάτων πρόσβασης, µε αποτέλεσµα το thread switch να είναι πολύ πιο γρήγορο από το context switch Σελίδα 7
ιεργασίες και Νήµατα Σελίδα 8
Πλεονεκτήµατα Χρήσης Νηµάτων (αντί Συνεργαζοµένων ιεργασιών) Απόκριση / απόδοση συστήµατος ιαµοιρασµός πόρων (Resource Sharing) Οικονοµία πόρων Εκµετάλλευση αρχιτεκτονικών πολλαπλών επεξεργαστών Πρόβληµα: Έλεγχος συγχρονισµού (concurrency control problems) Σελίδα 9
Υλοποίηση Νηµάτων σε Επίπεδο Χρήστη (User Threads) Η διαχείριση των νηµάτων γίνεται από βιβλιοθήκες που εκτελούνται σε κατάσταση χρήστη (user mode). Αποφεύγεται η επικοινωνία µε το σύστηµα/πυρήνα (δεν πραγµατοποιούνται κλήσεις συστήµατος) Τα νήµατα εκτελούνται µέσα από µια κοινή διεργασία Ένα νήµα αφήνειτηνκμεµε κλήση της ρουτίνας εναλλαγής, µε λειτουργία που προκαλεί έµµεσα εναλλαγή ή µε την λήξη ενός µετρητή Συνήθως δεν υποστηρίζεται κατανοµή του χρόνου εκτέλεσης της διεργασίας στα διάφορα νήµατα που έχει µέσα της Παραδείγµατα: POSIX pthreads, Mach C-threads, Solaris threads Σελίδα 10
Νήµατα σε Επίπεδο Χρήστη (συνέχεια) Πλεονεκτήµατα: Τα νήµατα χρήστη εναλλάσσονται (θεωρητικά) γρηγορότερα από τα νήµατα πυρήνα. Ωστόσο, στην πράξη, καλές υλοποιήσεις νηµάτων σε επίπεδο πυρήνα [Linux] εναλλάσσονται µε συναφήαπόδοση Μειονεκτήµατα: Ένα νήµα µπορεί να µονοπωλήσει τον χρόνο εκτέλεσης µιας διεργασίας (λιµοκτονία (starvation) των υπολοίπων νηµάτων) εν γίνεται εκµετάλλευση της συµµετρικής πολυεπεξεργασίας Όταν µπλοκαριστεί ένα νήµα µέσα σε κάποια λειτουργία Ι/Ο τότε µπλοκάρονται όλα τα νήµατα που εκτελούνται µέσα στην ίδια διεργασία (γιατί το ΛΣ δεν «γνωρίζει» την ύπαρξη τους) Σελίδα 11
Υλοποίηση Νηµάτων σε Επίπεδο Συστήµατος/Πυρήνα (Kernel Threads) Τα νήµατα πυρήνα υλοποιούνται στον πυρήνα του ΛΣ, και οι αντίστοιχες βιβλιοθήκες χρησιµοποιούν κλήσεις συστήµατος Σε αυτή την περίπτωση ο πυρήνας χρονοδροµολογεί κάθε νήµα εντός της µονάδας χρόνου που αναλογεί στην διεργασία µέσα στην οποία εκτελούνται τα νήµατα Υπάρχει περισσότερος φόρτος στο σύστηµα λόγω της εναλλαγής κατάστασης χρήστη <-> σύστηµα (user mode <-> system mode) και την διαχείριση πιο πολύπλοκων περιβαλλόντων, αλλά οι αρχικές µετρήσεις απόδοσης δείχνουν αµελητέα αύξηση στο χρόνο Παραδείγµατα: Windows 95/98/NT/2000, Solaris,Tru64 UNIX, BeOS, Linux Σελίδα 12
Νήµατα σε Επίπεδο Πυρήνα (συνέχεια) Πλεονεκτήµατα: Αποτροπή της µονοπώλησης της µονάδας χρόνου µιας διεργασίας από ένα νήµα (ενώ υπάρχουν και άλλα προς εκτέλεση) Το µπλοκάρισµα ενόςνήµατος σε Ι/Ο δεν συνεπάγεται µπλοκάρισµα και των άλλων νηµάτων που εκτελούνται µέσα στην ίδια διεργασία Μια διεργασία µπορεί να εκµεταλλευτεί (πιο εύκολα) τη συµµετρική πολυεπεξεργασία του ΛΣ και να τρέχει γρηγορότερα µε κάθεκμε που προστίθεται στο σύστηµα Μειονεκτήµατα: Ταχύτητα εναλλαγής νηµάτων Σελίδα 13
Μοντέλα Πολλαπλών Νηµάτων (Multithreading Models) Ανάλογα µε τοαντολσυποστηρίζεινήµατα σε επίπεδο πυρήνα αλλά και το πως αυτά αντιστοιχίζονται σε νήµατα επιπέδου χρήστη, υπάρχουν διάφορες προσεγγίσεις υλοποίησης: Πολλά σε Ένα (Many-to-One) Ένα προς Ένα (One-to-One) Πολλά προς Πολλά (Many-to-Many) Σελίδα 14
Πολλά σε Ένα (Many-to-One) Πολλά νήµατα επιπέδου χρήστη αντιστοιχούν σε ένα νήµα πυρήνα Χρησιµοποιείται σε συστήµατα που δεν υποστηρίζουν νήµατα πυρήνα Σελίδα 15
Μοντέλο Πολλά σε Ένα Σελίδα 16
Ένα προς Ένα (One-to-One) Κάθε νήµα χρήστη αντιστοιχεί σε ένα νήµα πυρήνα Παραδείγµατα: - Windows 95/98/NT/2000 -OS/2 Σελίδα 17
Μοντέλο Ένα προς Ένα Σελίδα 18
Μοντέλο Πολλά προς Πολλά Επιτρέπει σε πολλά νήµατα χρήστη να αντιστοιχιστούν σε πολλά νήµατα πυρήνα Επιτρέπει στο ΛΣ να δηµιουργήσει επαρκή αριθµό νηµάτων πυρήνα Solaris 2 Windows NT/2000 µε το πακέτο ThreadFiber Σελίδα 19
Μοντέλο Πολλά προς Πολλά (συνέχεια) Σελίδα 20
Θέµατα Νηµάτων (Threading Issues) Σηµασιολογία των κλήσεων συστήµατος fork() και exec() εξαµενές νηµάτων (Thread pools) Ακύρωση νηµάτων (Thread cancellation) Χειρισµός σηµάτων (Signal handling) εδοµένα σχετικά µε τανήµατα (Thread specific data) Σελίδα 21
Pthreads (POSIX Threads) Μια τυποποίηση POSIX (IEEE 1003.1c) API για δηµιουργία και συγχρονισµό νηµάτων σε περιβάλλον Unix To API προσδιορίζει τους τύπους (types), τις επικεφαλίδες των ρουτινών (interfaces), και την συµπεριφορά των ρουτινών της αντίστοιχης βιβλιοθήκης Η υλοποίηση της βιβλιοθήκης εναπόκειται στο ΛΣ Σελίδα 22
Νήµατα στο Solaris 2 Σελίδα 23
ιεργασίες στο Solaris 2 Σελίδα 24
Νήµατα στα Windows 2000 Υλοποιούν την αντιστοίχηση «Ένα προς Ένα» Κάθε νήµα περιέχει: ένα thread id ένα σύνολο από καταχωρητές (register set) ξεχωριστές στοίβες για το χρήστη και τον πυρήνα ιδιωτικό χώρο αποθήκευσης δεδοµένων Σελίδα 25
Νήµατα στο Linux (Linux Threads) To Linux αναφέρεται σε αυτά ως εργασίες (tasks) παρά ως νήµατα Ηδηµιουργία νηµάτων γίνεται µέσω της κλήσης συστήµατος clone() H Clone() επιτρέπει σε µια εργασία παιδί να µοιράζεται το χώρο διευθύνσεων της πατρικής εργασίας (διεργασίας) Σελίδα 26
Νήµατα στην Java Τα νήµατα στη Java µπορούν να δηµιουργηθούν µέσω: Επέκτασης του Thread class Υλοποίησης της διεπαφής Runnable Την διαχείριση των νηµάτων στη Java αναλαµβάνει η JVM Σελίδα 27
Κατάσταση Νηµάτων στην Java Σελίδα 28
Συγχρονισµός ιεργασιών σε Κοινή Μνήµη (Process Synchronization)
Εισαγωγή Θεωρητικό υπόβαθρο Το πρόβληµα τουκρίσιµου τµήµατος (the critical-section problem) Υλικό συγχρονισµού Σηµατοφορείς ή σηµαφόροι (semaphores) Κλασικά προβλήµατα συγχρονισµού Κρίσιµες περιοχές (critical regions) Ελεγκτές/παρακολουθητές (monitors) Σελίδα 30
Θεωρητικό Υπόβαθρο Η ταυτόχρονη πρόσβαση σε κοινά δεδοµένα µπορεί να οδηγήσει σε ασυνέπεια δεδοµένων (data inconsistency) Το πρόβληµα της συνέπειας υφίσταται στα νήµατα και στις διεργασίες(*) µε κοινήµνήµη που έχουν ταυτόχρονη πρόσβαση σε όλες τις καθολικές µεταβλητές και τα δυναµικά δεδοµένα ενός προγράµµατος Παρόµοιο πρόβληµα αντιµετωπίζει ο κώδικας του συστήµατος του οποίου η εκτέλεση µπορεί να διακοπεί από τις ρουτίνες χειρισµού διακοπών (interrupt handlers) που µε την σειρά τους µπορεί να κάνουν κλήσεις συστήµατος (που και αυτές µπορεί να διακοπούν...) (*) Σε αυτή την ενότητα χρησιµοποιούµε τον(γενικότερο) όρο διεργασία, ακόµα και όταν αναφερόµαστε σε νήµατα Σελίδα 31
Πρόσβαση σε Κοινή Μεταβλητή (Shared variable) Έστω πως δύο διεργασίες εκτελούν τον παρακάτω κώδικα: int i=0; /* κοινή µεταβλητή */ void body { i++; Ποιά η τιµή τηςµεταβλητής i αφού εκτελεσθούν οι διεργασίες; Μπορεί να είναι 1 ή 2, δεν το ξέρουµε εκ των προτέρων! Κάθε εκτέλεση µπορεί να δίνει διαφορετικό αποτέλεσµα! Σελίδα 32
Γιατί; Το πρόβληµα είναι πως η εκτέλεση της εντολής i++ δεν είναι (εγγυηµένα) ατοµική (atomic) δηλαδή χωρίς να υπάρχει πιθανότητα διακοπής της Ηεντολήi++ µεταφράζεται σε γλώσσα µηχανήςωςεξής: regx = mem[i_adr]; regx = regx+1; memy[i_adr] = regx; Η σειριακή εκτέλεση του κώδικα των διεργασιών δίνει διαφορετικό αποτέλεσµα από µια διαπλεκόµενη (interleaved) εκτέλεσή του Η εκτέλεση εξαρτάται από την (τυχαία) χρονοδροµολόγηση του ΛΣ Σελίδα 33
Σενάρια Εκτέλεσης Αρχικά η µεταβλητή i έχει την τιµή 0. Σενάριο Α thread 1: reg1 = mem[i_adr]; thread 1: reg1 = reg+1; thread 2: reg2 = mem[i_adr]; thread 2: reg2 = reg2+1; thread 2: mem[i_adr] = reg2; thread 1: mem[i_adr] = reg1; Η µεταβλητή i έχει την τιµή 1 Σενάριο B thread 1: reg1 = mem[i_adr]; thread 1: reg1 = reg+1; thread 1: mem[i_adr] = reg1; thread 2: reg2 = mem[i_adr]; thread 2: reg2 = reg2+1; thread 2: mem[i_adr] = reg2; Η µεταβλητή i έχει την τιµή 2 Σελίδα 34
Κατάσταση Συναγωνισµού (Race Condition) Κατάσταση Συναγωνισµού: Η κατάσταση όπου πολλές διεργασίες προσπελαύνουν και χειρίζονται ταυτόχρονα κοινά δεδοµένα Η τελική τιµή των κοινών δεδοµένων εξαρτάται από το πως θα δροµολογηθεί η επιµέρους εκτέλεσης του κώδικα των διεργασιών Για την αποτροπή καταστάσεων συναγωνισµού, οι διεργασίες µε κοινή µνήµη πρέπεινασυγχρονίζονται Σελίδα 35
Το Πρόβληµα τουκρίσιµου Τµήµατος (critical section problem) Ένας αριθµός από Ν διεργασίες εκτελούνται ταυτόχρονα µεταξύ τους και επιθυµούν να χρησιµοποιήσουν κάποια κοινά δεδοµένα Κάθε διεργασία έχει ένα τµήµα κώδικα που καλείται κρίσιµο τµήµα (critical section), στο οποίο προσπελαύνονται τα κοινά δεδοµένα Πρόβληµα: ιασφάλισε ότι όταν µια διεργασία εκτελεί κώδικα στο κρίσιµο τµήµα της, καµία άλλη διεργασία δεν θα να εκτελέσει στο κρίσιµο τµήµα της Σελίδα 36
Αφηρηµένη οµή των ιεργασιών while (1) { Κανονικός Κώδικας (δεν τίθεται θέµα συγχρονισµού) Κρίσιµο Τµήµα (critical section) Κανονικός Κώδικας (δεν τίθεται θέµα συγχρονισµού) Σελίδα 37
Οι 3 Απαιτήσεις της Λύσης του Κρίσιµου Τµήµατος 1. Αµοιβαίος αποκλεισµός (mutual exclusion): Αν η µια διεργασία εκτελεί κώδικα στο κρίσιµο τµήµα της, τότε καµία άλλη διεργασία δεν εκτελεί κώδικα στο κρίσιµο τµήµα της 2. Πρόοδος (progress): Αν δεν υπάρχει διεργασία που να βρίσκεται µέσα στο κρίσιµο τµήµα της και υπάρχουν διεργασίες που επιθυµούν να εισέλθουν στο κρίσιµο τµήµα τους, τότε η επιλογή της επόµενης διεργασίας που θα εισέλθει στο κρίσιµο τµήµα τηςδεν µπορεί να αναβάλλεται επ αόριστον Σελίδα 38
Οι 3 Απαιτήσεις της Λύσης του Κρίσιµου Τµήµατος (συνέχεια) 3. Πεπερασµένη Αναµονή (bounded waiting): Πρέπει να υπάρχει ένα όριο στο πλήθος των φορών που επιτρέπεται σε άλλες διεργασίες να εισέλθουν στο κρίσιµο τµήµα τους, αφότου µια διεργασία έχει εκδώσει αίτηση να εισέλθει στο κρίσιµο τµήµα τηςκαι µέχρι η αίτηση αυτή να ικανοποιηθεί Υποθέτουµε ότι κάθε διεργασία εκτελείται µε µη µηδενική ταχύτητα εν µπορεί να γίνει καµία υπόθεση η οποία να αφορά τη σχετική ταχύτητα των διαφόρων διεργασιών, ή το πλήθος των επεξεργαστών στους οποίους εκτελούνται οι διεργασίες Σελίδα 39
Πιο Λεπτοµερής οµή των ιεργασιών while (1) { Κανονικός Κώδικας (δεν τίθεται θέµα συγχρονισµού) Κρίσιµο Τµήµα (critical section) Κανονικός Κώδικας (δεν τίθεται θέµα συγχρονισµού) Κώδικας Εισόδου (entry code) Κώδικας Εξόδου (exit code) Σελίδα 40
Αλγόριθµος 1 (δύο ιεργασίες) Κοινή µεταβλητή turn (η διεργασίαp i εισέρχεται στο ΚΤ µόνο όταν turn=i): shared vars: int turn=0; process body: while (1) { while (turn!=i) { /* entry code */ do_critical_section(); turn=(i+1)%2; /* exit code */ Ικανοποιεί τον αµοιβαίο αποκλεισµό, αλλά όχι την πρόοδο (γιατί;) Σελίδα 41
Αλγόριθµος 2 (δύο ιεργασίες) Κοινή µεταβλητή boolean flag[2] (η διεργασία P i εισέρχεται στο ΚΤ µόνο όταν flag[i]= true): shared vars: boolean flag[2]={false,false; process body: while (1) { flag[i]=true; /* entry code */ while (flag[(i+1)%2]) { do_critical_section(); flag[i]=false; /* exit code */ Ικανοποιεί τον αµοιβαίο αποκλεισµό, αλλά όχι την πρόοδο (γιατί;) Σελίδα 42
Αλγόριθµος 3 (δύο ιεργασίες) Συνδυασµός των δύο προηγουµένων αλγορίθµων: shared vars: int turn=0; boolean flag[2]={false,false; process body: while (1) { flag[i]=true; turn=(i+1)%2; /* entry code */ while ( (flag[(i+1)%2]) && (turn=(i+1)%2) ) { do_critical_section(); flag[i]=false; /* exit code */ Ικανοποιεί και τις τρεις απαιτήσεις (γιατί;) Σελίδα 43
Αλγόριθµός για πολλές ιεργασίες Ο Αλγόριθµος Bakery Πριν την είσοδό της στο κρίσιµο τµήµα, µια διεργασία P i λαµβάνει έναν αριθµό σειράςt i. Το σύστηµα αριθµοδότησης δηµιουργεί αριθµούς σειράς µε αύξουσα σειρά αρίθµησης, π.χ., 1,2,3,3,3,4,5 Η διεργασία µε τονµικρότερο αριθµό σειράς εισέρχεται στο κρίσιµο τµήµα Αν οι διεργασίες P i και P j έχουν τον ίδιο αριθµό σειράς, αν i < j, τότε εξυπηρετείται πρώτα η P i, αλλιώς πρώτα η P j Συµβολισµοί: (a,b) before (c,d) <=> ((a < c) ((a = c) && (b < d)) max (a 0,, a n-1 ) = k, k a i για i =0,, n 1 Σελίδα 44
Ο Αλγόριθµος Bakery (συνέχεια) shared vars: boolean choosing[n]={false,,false; int t[n]={0,,0; process body: Παραλαβή while (1) { αριθµού σειράς choosing[i] = true; Έλεγχος t[i] = max(t[0],t[1],, t[n-1])+1; προτεραιότητας choosing[i] = false; for (j=0; j<n; j++) { while (choosing[j]) { while ((t[j]!=0) && (t[j],j) before (t[i],i)) { do_critical_section(); t[i] = 0; Σελίδα 45
Υλικό Συγχρονισµού (Synchronization Hardware) Μερικοί επεξεργαστές υποστηρίζουν την εξής εντολή TestAndSet για τον ατοµικό έλεγχο και αλλαγή µιας θέσης µνήµης (µεταβλητής) Ησυµβολική υλοποίηση της εντολής δίνεται ως εξής: boolean TestAndSet(boolean *target) { boolean value_read = *target; *target = true; return value_read; Η εντολή εκτελείται χωρίς διακοπή (εγγυηµένα) Σελίδα 46
Αλγόριθµος Κρίσιµου Τµήµατος µε Χρήση της TestandSet shared vars: boolean lock=false; process body: while (1) { while (TestAndSet(&lock)) { do_critical_section(); lock = false; Σελίδα 47
Υλικό Συγχρονισµού (συνέχεια) Εξατοµικευµένη εναλλαγή δύο µεταβλητών void Swap(boolean *a, boolean *b) { boolean tmp; tmp = *a; *a = *b; *b = tmp; Σελίδα 48
Αλγόριθµος Κρίσιµου Τµήµατος µε Χρήση της Swap shared vars: boolean lock=false; process body: boolean key; while (1) { key=true; do {Swap(&lock,&key); while (key); do_critical_section(); lock = false; Σελίδα 49
Σηµαφόροι ή Σηµατοφορείς (Semaphores) Εργαλείο συγχρονισµού που δεν απαιτεί ενεργό αναµονή (busy waiting) της διεργασίας, εκτελώντας κάποιες εντολές σε βρόγχο Μια µεταβλητή τύπου s σηµατοφορέα µπορεί να προσπελαστεί µέσω δύο ατοµικών πράξεων wait (ή down) και signal (ή up), που µπορεί να θεωρηθεί πως υλοποιούνται ως εξής: void wait(int *s) { while (*s<=0) { *s--; void signal(int *s) { *s++; Σελίδα 50
Υλοποίηση Κρίσιµου Τµήµατος για n ιεργασίες µε Σηµαφόρους shared vars: semaphore s=1; process body: while (1) { wait(s); do_critical_section(); signal(s); Σελίδα 51
Υλοποίηση Σηµαφόρου typedef struct { int sval; /* value of semaphore */ struct procq pq;/* queue of waiting processes */ semaphore; void init(semaphore *s, int val) { s->sval=val; initq(s->pq); Σελίδα 52
Υλοποίηση Σηµαφόρου (συνέχεια) void wait(semaphore *s) { s->sval--; if (s->sval<0) { addq(s->pq,thisprocess()); suspend(); void signal(semaphore *s) { s->sval++; if (s->sval<==0) { resume(rmvq(s->pq)); Σελίδα 53
ΗΣηµαφόρος ως ένα Γενικό Εργαλείο Συγχρονισµού ιεργασία P i A signal(flag) ιεργασία P j wait(flag) B Χρήση της σηµαφόρου flag µε αρχική τιµή 0 Εκτέλεση του B στην P j µόνο αφού εκτελεστεί το A στην P i Σελίδα 54
Τύποι Σηµαφόρων Γενική σηµαφόρος (ή σηµαφόρος µετρητής) (general or counting semaphore) µπορεί να έχει µια οποιαδήποτε ακέραια τιµή υαδική σηµαφόρος (binary semaphore) µπορεί να παίρνει τιµές µόνο µεταξύ 0 και 1, κάτι που µπορεί να σηµαίνει ευκολότερη υλοποίηση Μια γενική σηµαφόρος µπορεί να υλοποιηθεί µε χρήσηδυαδικών σηµαφόρων Σελίδα 55
Υλοποίηση Γενικής Σηµαφόρου µε υαδικές Σηµαφόρους typedef struct { bin_semaphore s1,s2; int cnt; semaphore; void init(semaphore *s, int val) { bin_init(&s->s1,1); bin_init(&s->s2,0); s->cnt=val; Σελίδα 56
Υλοποίηση Γενικής Σηµαφόρου µε υαδικές Σηµαφόρους (συνέχεια) void wait(semaphore *s) { bin_wait(&s->s1); s->cnt--; if (s->cnt<0) { bin_signal(&s->s1); bin_wait(&s->s2); bin_signal(&s->s1); void signal(semaphore *s) { bin_wait(&s->s1); s->cnt++; if (s->cnt<=0) {bin_singal(&s->s2); else {bin_signal(&s->s1); Σελίδα 57
Κλασικά Προβλήµατα Συγχρονισµού Το πρόβληµα της αποθήκης πεπερασµένης χωρητικότητας (bounded-buffer problem) Το πρόβληµα αναγνωστών και εγγραφέων (readers and writers problem) Το πρόβληµα τωνσυνδαιτυµόνων φιλοσόφων (dining philosophers problem) Σελίδα 58
Αποθήκη Πεπερασµένης Χωρητικότητας Κοινά δεδοµένα: semaphore mutex,full,empty; Αρχικοποίηση: init(&mutex,1); init(&empty,n); init(&full,0); Σελίδα 59
ιεργασία Παραγωγός (producer) while (1) { παραγωγή αντικειµένου item wait(&empty); wait(&mutex); προσθήκη αντικειµένου item στην αποθήκευση signal(&mutex); signal(&full); Σελίδα 60
ιεργασία Καταναλωτής (consumer) while (1) { wait(&full); wait(&mutex); αποµάκρυνση αντικειµένου item από την αποθήκευση signal(&mutex); signal(&empty); κατανάλωση αντικειµένου item Σελίδα 61
Αναγνώστες και Εγγραφείς Κοινά δεδοµένα: semaphore mutex,write; int readers; Αρχικοποίηση: init(&mutex,1); init(&write,1); readers=0; Σελίδα 62
ιεργασία Εγγραφέας while (1) { wait(&write); γράψιµο δεδοµένων signal(&write); Σελίδα 63
ιεργασία Αναγνώστης while (1) { wait(&mutex); readers++; if (readers==1) {wait(&write); signal(&mutex); διάβασµα δεδοµένων wait(&mutex); readers--; if (readers==0) {signal(&write); signal(&mutex); Σελίδα 64
Οι Συνδαιτυµόνες Φιλόσοφοι Κοινά δεδοµένα: semaphore fork[n]; Αρχικοποίηση: init(&fork[0],1); init(&fork[1],1); init(&fork[n-1],1); Σελίδα 65
Ο Φιλόσοφος i while (1) { wait(&fork[i]); wait(&fork[(i+1)%n); επιτέλους τρώµε! signal(&fork[i]); signal(&fork[(i+1)%n]); ας σκεφτούµε λιγάκι Υπάρχει περίπτωση ο ένας φιλόσοφος να περιµένει τον άλλο σε κύκλο; Σελίδα 66
Κρίσιµες Περιοχές (Critical Regions) οµή υψηλού επιπέδου για την υποστήριξη του συγχρονισµού κρίσιµων τµηµάτων, ως εξής: Μια κοινή µεταβλητή v τύπου T ορίζεται ως: T (shared) v; Η v µπορεί να προσπελαστεί µόνο µέσω της εντολής region: region v when B do S; όπου η διεργασία ελέγχει την συνθήκη B και αν είναι αληθής, τότε εκτελεί την οµάδα εντολών S, διαφορετικά ανατέλλεται µέχρι η συνθήκη B να γίνει αληθής. Τόσο ο έλεγχος της συνθήκης B όσοκαιηεκτέλεσητηςοµάδας εντολών S γίνεται ατοµικά (το σύστηµα εγγυάται πως δεν παρεµβάλλονται άλλες διεργασίες σε αυτή την περιοχή). Σελίδα 67
Αποθήκη Πεπερασµένης Χωρητικότητας µε Κρίσιµες Περιοχές typedef struct { item slots[n]; int in,out,cnt; buffer; buffer (shared) b; b.cnt=0; b.in=b.out=0; slots[n-1] slots[0] direction of in, out Σελίδα 68
Αποθήκη Πεπερασµένης Χωρητικότητας (συνέχεια) ιεργασία Παραγωγός while (1) { item=produce(); region b when (b.cnt<n) { b.slots[b.in]=item; b.in=(b.in+1)%n; b.cnt++; ιεργασία Καταναλωτής while (1) { region b when (b.cnt>0) { item=b.slots[b.out]; b.out=(b.out+1)%n; b.cnt--; consume(item); Σελίδα 69
Υλοποίηση της οµής region µε Σηµαφόρους Συσχετισµός µε τηνκρίσιµη περιοχή, των ακόλουθων µεταβλητών: semaphore mx, q1, q2; int n1, n2; Ηαµοιβαίως αποκλειόµενη πρόσβαση στο κρίσιµο τµήµα παρέχεται από τη mx Αν µια διεργασία δεν µπορεί να εισέλθει στην κρίσιµη περιοχή λόγω του ότι η συνθήκη B είναι ψευδής, αρχικά περιµένει στη σηµαφόρο q1, και µετά στη σηµαφόρο q2 πριν µπορέσει να επανεξετάσει την τιµή τηςβ. Καταγραφή του πλήθους των διεργασιών που περιµένουν στις q1 και q2, γίνεται µε τηn1 και τη n2 Υποθέτουµε διάταξηfifo στην αναµονή των διεργασιών για µια σηµαφόρο Σελίδα 70
Υλοποίηση οµής region (συνέχεια) typedef struct { semaphore mx,q1,q2; int n1,n2; region; void init(region *r) { init(&r->mx,1); init(&r->q1,0); init(&r->q2,0); n1=n2=0; Σελίδα 71
Υλοποίηση οµής region (συνέχεια) void enter(region *r, Condition B, Code S) { wait(&r->mx); while (!B) { r->n1++; if (r->n2>0) {signal(&r->q2); else {signal(&r->mx); wait(&r->q1); r->n1--; r->n2++; if (r->n1>0){signal(&r->q1); else {signal(&r->q2); wait(&r->q2); r->n2--; S; if (r->n1>0) {signal(&r->q1); else if (r->n2>0) {signal(&r->q2); else {signal(&r->mx); Σελίδα 72
Ελεγκτές/Παρακολουθητές (Monitors) οµή υψηλού επιπέδου που επιτρέπει την ασφαλή πρόσβαση ενός αφηρηµένου τύπου δεδοµένων µεταξύ ταυτόχρονων διεργασιών monitor monitor-name { δηλώσεις κοινών µεταβλητών procedure P1 ( ) { procedure P2 ( ) { procedure Pn ( ) { { κώδικας αρχικοποίησης Οι ρουτίνες πρόσβασης Pi εκτελούνται συγχρονισµένα, µε εγγυηµένο τον αµοιβαίο αποκλεισµό ανάµεσα σε διεργασίες Σελίδα 73
Ελεγκτές/Παρακολουθητές (συνέχεια) Για να επιτραπεί σε µια διεργασία να περιµένει µέσα σε έναν ελεγκτή, πρέπει να δηλωθεί αντίστοιχη µεταβλητή συνθήκης (condition variable): condition x; Μπορεί να χρησιµοποιηθεί µόνο µε τις λειτουργίες wait και signal Ηλειτουργίαx.wait() σηµαίνει ότι η διεργασία αναστέλλεται Ηλειτουργίαx.signal() εκκινεί (µόνο) µια από τις διεργασίες που έχουν ανασταλεί, αν υπάρχει (διαφορετικά δεν κάνει τίποτα) Ηλειτουργίαx.signalAll() εκκινεί όλες τις διεργασίες (αν υπάρχουν) που έχουν ανασταλεί µέσω της µεταβλητής x Σελίδα 74
Αναµονή µε Προτεραιότητες Αναµονή υπό συνθήκη (conditional-wait): x.wait(c); c ακέραιος που ελέγχεται όταν εκτελείται η λειτουργία wait Ητιµή τουc (αριθµός προτεραιότητας) αποθηκεύεται µε το όνοµα της διεργασίας που ανεστάλη Ηεκτέλεσητηςx.signal, ενεργοποιεί τη διεργασία µε τη µικρότερη προτεραιότητα Σελίδα 75
Σχηµατική Θεώρηση ενός Ελεγκτή Σελίδα 76
Ελεγκτής µε ΜεταβλητέςΣυνθήκης Σελίδα 77
Συνδαιτυµόνες Φιλόσοφοι µε Ελεγκτή monitor dining_phils { enum {thinking,hungry,eating state[n]; condition self[n]; void pickup(int i); void putdown(int i); void test(int i); void init() { int i; for (i=0; i<5; i++) {state[i] = thinking; Σελίδα 78
Συνδαιτυµόνες Φιλόσοφοι (συνέχεια) void pickup(int i) { state[i] = hungry; test[i]; if (state[i]!= eating) { self[i].wait(); void putdown(int i) { state[i] = thinking; test((i+n-1)%n); /* test left */ test((i+1)%n); /* test right */ Σελίδα 79
Συνδαιτυµόνες Φιλόσοφοι (συνέχεια) void test(int i) { if ( (state[i] == hungry) && (state[(i+n-1)%5]!= eating) && (state[(i+1)%n]!= eating) ) { state[i] = eating; self[i].signal(); Σελίδα 80
Υλοποίηση Ελεγκτών µε Σηµαφόρους Για κάθε ελεγκτή, καθολικές µεταβλητές: semaphore mutex,next; int next-count; Επιπλέον, για κάθε µεταβλητή συνθήκης, µεταβλητές: semaphore x-sem; int x-count; Αρχικοποίηση του ελεγκτή: init(&mutex,1); init(&next,0); next-count=0; Και αρχικοποίηση για κάθε µεταβλητή συνθήκης: init(&x-sem); x-count=0; Σελίδα 81
Υλοποίηση Ελεγκτών µε Σηµαφόρους (συνέχεια) Για κάθε ρουτίνα πρόσβασης Pi, το σώµα της αντικαθίσταται µε : procedure Pi ( ) { wait(&mutex); κυρίως σώµα τηςpi if (next-count > 0) {signal(&next); else {signal(&mutex); Σελίδα 82
Υλοποίηση Ελεγκτών µε Σηµαφόρους (συνέχεια) Ηλειτουργίαx.wait() µπορεί να υλοποιηθεί ως εξής: x-count++; if (next-count > 0) { signal(&next); else { signal(&mutex); wait(&x-sem); x-count--; Σελίδα 83
Υλοποίηση Ελεγκτών µε Σηµαφόρους (συνέχεια) Ηλειτουργίαx.signal() µπορεί να υλοποιηθεί ως εξής: if (x-count > 0) { next-count++; signal(&x-sem); wait(&next); next-count--; Σελίδα 84
Υλοποίηση Ελεγκτών µε Σηµαφόρους (συνέχεια) Ηλειτουργίαx.signalAll() µπορεί να υλοποιηθεί ως εξής: if (x-count > 0) { next-count++; for (c=x-count; c > 0; c--) { signal(&x-sem); wait(&next); next-count--; Σελίδα 85
Αδιέξοδο και Λιµοκτονία (Deadlock and Starvation) Αδιέξοδο δύο ή περισσότερες διεργασίες περιµένουν απεριόριστα για ένα γεγονός που µπορεί να προκληθεί µόνο από µια από τις δυο Έστω S και Q δύο σηµαφόροι µε αρχικήτιµή 1 P 0 P 1 wait(s); wait(q); wait(q); M signal(s); signal(q) wait(s); M signal(q); signal(s); Λιµοκτονία απεριόριστη αναµονή. Μια διεργασία µπορεί να µην φύγει από την ουρά της σηµαφόρου στην οποία έχει ανασταλεί Σελίδα 86