Σύγχρονη επικοινωνία με ανταλλαγή μηνυμάτων (CSP message passing model) Ταυτόχρονος Προγραμματισμός 1 lalis@inf.uth.gr
Σύγχρονα κανάλια επικοινωνίας Μέρος του Communicating Sequential Processes (CSP) που πρότεινε ο Hoare ως φορμαλισμό για την περιγραφή ταυτόχρονων διεργασιών/συστημάτων To CSP είναι ένα θεωρητικό εργαλείο Που επηρέασε τον σχεδιασμό πολλών πραγματικών συστημάτων και γλωσσών προγραμματισμού Occam, Ada, Plan9/Inferno/Limbo, Go Σημείωση: η σύνταξη που θα χρησιμοποιήσουμε είναι απλοποιημένη και ενδεικτική -- δεν αντιστοιχεί στην «επίσημη» σύνταξη/σημασιολογία του CSP Ταυτόχρονος Προγραμματισμός 2 lalis@inf.uth.gr
Βασικό μοντέλο channel P1 P2 Αλληλεπίδραση μέσω καναλιών επικοινωνίας Κάθε κανάλι είναι σημείο-προς-σημείο: συνδέει δύο νήματα/διεργασίες αξιόπιστο: δεν χάνονται μηνύματα μιας κατεύθυνσης (half duplex) Τα κανάλια μεταφέρουν μηνύματα που έχουν συγκεκριμένο τύπο (πολύπλοκη δομή) Το σύνολο των συνεργαζόμενων νημάτων/διεργασιών και καναλιών θεωρείται γνωστό εκ των προτέρων Ταυτόχρονος Προγραμματισμός 3 lalis@inf.uth.gr
Βασικές λειτουργίες chan!msg Αποστολή μηνύματος msg στο κανάλι chan Ο αποστολέας μπλοκάρει μέχρι το μήνυμα να παραληφθεί από την διεργασία που βρίσκεται στην άλλη άκρη του καναλιού chan?msg Παραλαβή μηνύματος msg από το κανάλι chan Ο παραλήπτης μπλοκάρει μέχρι η διεργασία που βρίσκεται στην άλλη άκρη του καναλιού να στείλει ένα μήνυμα συμβατού τύπου Ταυτόχρονος Προγραμματισμός 4 lalis@inf.uth.gr
Δομή μηνυμάτων / συμβατότητα Αποστολέας: στέλνει μια ή περισσότερες τιμές μπορεί να είναι σταθερές ή τιμές μεταβλητών Παραλήπτης: προσδιορίζει μια ή περισσότερες τιμές, ή μια ή περισσότερες διευθύνσεις μεταβλητών κάθε διεύθυνση αντιστοιχεί σε «ελεύθερη» μεταβλητή όπου θα αντιγραφούν οι τιμές του μηνύματος που θα παραληφθεί Κανόνες συμβατότητας Αν ο παραλήπτης δίνει μια τιμή, το αντίστοιχο πεδίο του μηνύματος πρέπει να έχει αυτή την τιμή Αν ο παραλήπτης δίνει μια διεύθυνση μεταβλητές, το αντίστοιχο πεδίο του μηνύματος πρέπει να έχει μια τιμή που είναι συμβατή με τον τύπο της μεταβλητής Ταυτόχρονος Προγραμματισμός 5 lalis@inf.uth.gr
Συγχρονισμός αποστολής/παραλαβής Η αποστολή και παραλαβή ενός μηνύματος μπορεί να θεωρηθεί ως ένα συμμετρικό ραντεβού Η διεργασία που επιχειρεί πρώτη την επικοινωνία θα περιμένει μέχρι η άλλη διεργασία θελήσει να πραγματοποιήσει την «αντίστροφη» επικοινωνία Κάθε κανάλι έχει το πολύ ένα μήνυμα που βρίσκεται καθοδόν, από τον αποστολέα προς τον παραλήπτη Δεν χρειάζονται ενδιάμεσες αποθήκες Μπορεί όμως να υπάρξουν αδιέξοδα! Ταυτόχρονος Προγραμματισμός 6 lalis@inf.uth.gr
Ραντεβού: όποια διεργασία «φτάσει» πρώτη στο «σημείο συνάντησης» περιμένει την άλλη P2 exec path P1 exec path chan!msg chan?msg «σημείο» συνάντησης Ταυτόχρονος Προγραμματισμός 7 lalis@inf.uth.gr
int a; a = 5; P1 P2 int b; chan!a <int,5> chan?b (b is 5) Ταυτόχρονος Προγραμματισμός 8 lalis@inf.uth.gr
int a; a = 5; P1 P2 int b; chan?b chan!a <int,5> (b is 5) Ταυτόχρονος Προγραμματισμός 9 lalis@inf.uth.gr
Παραγωγός-καταναλωτής P Data C Ταυτόχρονος Προγραμματισμός 10 lalis@inf.uth.gr
P: Data d; /* produce d */ chan!d; C: Data d; chan?d; /* consume d */ Ταυτόχρονος Προγραμματισμός 11 lalis@inf.uth.gr
Αποφυγή μπλοκαρίσματος Αν ο παραγωγός επιχειρήσει να στείλει δεδομένα ενώ δεν περιμένει ο καταναλωτής, θα μπλοκάρει Η σύγχρονη επικοινωνία «δένει» πολύ στενά τα εμπλεκόμενα μέρη και εμποδίζει την υλοποίηση πιο ευέλικτων σχημάτων αλληλεπίδρασης Eιδική δομή ταυτόχρονης αναμονής για την παραλαβή μηνύματος από διαφορετικές διεργασίες Ανάλογα με τον αποστολέα, μπορεί να γίνεται αναμονή για μήνυμα διαφορετικού τύπου Ταυτόχρονος Προγραμματισμός 12 lalis@inf.uth.gr
Ταυτόχρονη αναμονή για παραλαβή μηνύματος από διαφορετικά κανάλια select { chan 1?msg 1 : S 1 chan 2?msg 2 : S 2 chan N?msg N : S N Η σειρά ελέγχου των περιπτώσεων στην δομή αναμονής είναι μη ντετερμινιστική αλλά δίκαιη Η select τερματίζει όταν παραληφθεί ένα από τα μηνύματα μέσω του προδιαγεγραμμένου καναλιού, και εκτελεστεί η αντίστοιχη ομάδα εντολών Ταυτόχρονος Προγραμματισμός 13 lalis@inf.uth.gr
Αναμονή πάνω σε σύνολο καναλιών chan[]?(msg,idx) Επιστρέφει το μήνυμα msg και την θέση idx (στον πίνακα) του καναλιού από το οποίο έφτασε το μήνυμα αν δεν ενδιαφέρει η θέση idx τότε βάζουμε void Ισοδύναμο με: select { chan[0]?msg: idx=0; chan[1]?msg: idx=1; chan[n-1]?msg: idx=n-1; Ταυτόχρονος Προγραμματισμός 14 lalis@inf.uth.gr
Ταυτόχρονη αναμονή για παραλαβή μηνύματος υπό συνθήκη select { cond 1 && chan1?msg1: S 1 cond 2 && chan2?msg2: S 2 cond N && chann?msgn: S N Για να παραληφθεί ένα μήνυμα (αν είναι διαθέσιμο) πρέπει η αντίστοιχη συνθήκη να είναι αληθής Η σειρά ελέγχου των διαφόρων περιπτώσεων στην select είναι μη ντετερμινιστική αλλά δίκαιη Κάποιες περιπτώσεις παραλαβής μπορεί να μην έχουν συνθήκη, οπότε είναι διαρκώς «ενεργοποιημένες» Ταυτόχρονος Προγραμματισμός 15 lalis@inf.uth.gr
Παραγωγός-καταναλωτής με αποθήκη P Data Buf "GET" C Data Ταυτόχρονος Προγραμματισμός 16 lalis@inf.uth.gr
Buf: Data buf[n]; int in = 0, out = 0, n = 0; select { (n<n) && chanp?buf[in]: { in = (in+1)%n; n++; (n>0) && chanc?"get": { chanc!buf[out]; out = (out+1)%n; n--; P: /* produce d */ chanp!d; C: chanc!"get"; chanc?d; /* consume d */ Ταυτόχρονος Προγραμματισμός 17 lalis@inf.uth.gr
Πολλοί παραγωγοί-καταναλωτές P1 C1 P2 Data Buf "GET" C2 Data...... PN CM Ταυτόχρονος Προγραμματισμός 18 lalis@inf.uth.gr
Buf: Data buf[n]; int in = 0, out = 0, n = 0; int pid; select { (n<n) && chanp[]?(buf[in],void): { in = (in+1)%n; n++; (n>0) && chanc[]?("get",pid): { chanc[pid]!buf[out]; out = (out+1)%n; n--; Pi: /* produce d */ chanp[i]!d; Ci: chanc[i]!"get"; chanc[i]?d; /* consume d */ Ταυτόχρονος Προγραμματισμός 19 lalis@inf.uth.gr
Αμοιβαίος αποκλεισμός Lock "LOCK" "UNLOCK" P1 P2 PN... Ταυτόχρονος Προγραμματισμός 20 lalis@inf.uth.gr
Lock: int pid=-1; select { (pid == -1) && chan[]?("lock",pid): { (pid!= -1) && chan[pid]?("unlock",void) { pid = -1; Pi: chan[i]!"lock"; /* CS */ chan[i]!"unlock"; Ταυτόχρονος Προγραμματισμός 21 lalis@inf.uth.gr
Lock: int pid=-1; Pi: chan[i]!"lock"; /* CS */ χρειάζονται αυτές οι συνθήκες αναμονής; select { (pid == -1) && chan[]?("lock",pid): { (pid!= -1) && chan[pid]?"unlock": { pid = -1; chan[i]!"unlock"; Ταυτόχρονος Προγραμματισμός 22 lalis@inf.uth.gr
Lock: chan[]?("lock",void); chan[]?("unlock",void); Pi: chan[i]!"lock"; /* CS */ chan[i]!"unlock"; Ταυτόχρονος Προγραμματισμός 23 lalis@inf.uth.gr
Γενικός σηματοφόρος Sem "DOWN" "UP" P1 P2 PN... Ταυτόχρονος Προγραμματισμός 24 lalis@inf.uth.gr
Sem: int val = ; select { (val>0) && chan[]?("down",void): { val--; chan[]?("up",void): { val++; Pi: chan[i]!"down"; Pj: chan[j]!"up"; Ταυτόχρονος Προγραμματισμός 25 lalis@inf.uth.gr
Συνδαιτυμόνες φιλόσοφοι P0 P1 PN-1... "GET" "PUT" Fork0 Fork1 Fork2 ForkN-1... Ταυτόχρονος Προγραμματισμός 26 lalis@inf.uth.gr
Fork(i): 0<= i < N select { chan[(i*2-1)%n]?"get": chan[(i*2-1)%n]?"put"; chan[i*2]?"get": chan[i*2]?"put"; Pi : 0<= i < N chan[i*2]!"get"; chan[i*2+1]!"get"; /* eat */ chan[i*2]!"put"; chan[i*2+1]!"put"; Ταυτόχρονος Προγραμματισμός 27 lalis@inf.uth.gr
Κόσκινο του Ερατοσθένη filter multiples of 1st prime filter multiples of 2nd prime filter multiples of 3rd prime filter multiples of 4th prime sink Sieve1 Sieve2 Sieve3 Sieve4 Sieve5 2 3 5 7 (11) 2 2 3 3 4 5 5 6 7 7 8 9 10 11 12 13 11 13 Ταυτόχρονος Προγραμματισμός 28 lalis@inf.uth.gr
Sieve0: seed Sieve1: filter 2 Sieve2: filter 3 Sieve3: filter 5 Sieve4: filter 7 Sieve5: sink Ταυτόχρονος Προγραμματισμός 29 lalis@inf.uth.gr
Sieve(0): (seed) for (n=2; n<=100; n++) { chan[0]!n; Sieve(5): (sink) int n; chan[4]?n; print(n); Sieve(i): 1<= i <=4 int p, mp, n; chan[i-1]?p; // prime print(p); mp = p*p; // start filter chan[i-1]?n; while (n > mp) { mp = mp+p; // advance filter if (n < mp) { chan[i]!n; // passed filter Ταυτόχρονος Προγραμματισμός 30 lalis@inf.uth.gr
Πολλαπλασιασμός πινάκων 1 2 3 4 5 6 7 8 9 1 0 2 0 1 2 1 0 0 = 4 2 6 10 5 18 16 8 30 2 0 1 2 1 0 0 0 1 Zero 0,0,0 MulAdd 8,0,4 MulAdd 18,5,4 MulAdd 18,5,10 Result 4 5 6 Ταυτόχρονος Προγραμματισμός 31 lalis@inf.uth.gr
Source 102 2 0 1 Source 012 2 1 0 Source 100 0 0 1 0,0,0 Zero MulAdd 2,0,1 MulAdd 6,2,1 MulAdd 6,2,4 Result 1 2 3 2 2 0 0 Vi,j 1 0 1 0 1 0,0,0 Zero MulAdd 8,0,4 MulAdd 18,5,4 MulAdd 18,5,10 Result 4 Hi,j 5 Hi,j+1 6 2 2 0 0 Vi+1,j 1 0 1 0 1 0,0,0 Zero MulAdd 14,0,7 MulAdd 30,8,7 MulAdd 30,8,16 Result 7 8 9 2 2 0 0 1 0 1 0 1 Sink Sink Sink Ταυτόχρονος Προγραμματισμός 32 lalis@inf.uth.gr
Source(i): 0<= i <N for (k=0; k<n-1; k++ ) { chanv[0][i]!b[i][k]; Sink(i): 0<= i <N for (k=0; k<n-1; k++ ) { chanv[n][i]?dmy; Zero(i): 0<= i <N for (k=0; k<n-1; k++ ) { chanh[i][0]!0; Result(i): 0<= i <N MulAdd(i,j): 0<= i,j <N int s,b; for (k=0; k<n-1; k++) { chanv[i][j]?b; // B[i][k] chanh[i][j]?s; // sum s = s + A[i][j]*b; chanv[i+1][j]!b; chanh[i][j+1]!s; for (k=0; k<n-1; k++ ) { chanh[i][n]?r[i][k]; Ταυτόχρονος Προγραμματισμός 33 lalis@inf.uth.gr
Υλοποίηση CSP Σύστημα με κοινή μνήμη Κάθε λογικό κανάλι μπορεί να υλοποιηθεί ως μια ξεχωριστή ουρά μηνυμάτων σε κοινόχρηστη μνήμη Οι λειτουργίες αποστολής/παραλαβής/αναμονής υλοποιούνται με τους κλασικούς μηχανισμούς συγχρονισμού, π.χ., semaphores Σύστημα χωρίς κοινή μνήμη Κάθε λογικό κανάλι μπορεί να υλοποιηθεί ως μια ουρά που βρίσκεται στον αποστολέα ή/και στον παραλήπτη Οι λειτουργίες αποστολής/παραλαβής/αναμονής υλοποιούνται με κάποιο μηχανισμό απομακρυσμένης επικοινωνίας, π.χ., sockets Ταυτόχρονος Προγραμματισμός 34 lalis@inf.uth.gr
P1 P2 CSP API Msg Queue Ταυτόχρονος Προγραμματισμός 35 lalis@inf.uth.gr
P1 P2 CSP API CSP API Msg Queue Ταυτόχρονος Προγραμματισμός 36 lalis@inf.uth.gr
P1 P2 CSP API CSP API Msg Queue Ταυτόχρονος Προγραμματισμός 37 lalis@inf.uth.gr
Ραντεβού στην Ada Ταυτόχρονος Προγραμματισμός 43 lalis@inf.uth.gr
Κλήση διαδικασίας με ραντεβού Συνδυασμός σχήματος συγχρονισμού του CSP για επικοινωνία μέσω κλήσεων διαδικασίας Μέσω της εντολής accept, γίνεται ρητή αποδοχή μιας κλήσης του API, με μπλοκάρισμα μέχρι ένα άλλο νήμα να πραγματοποιήσει μια τέτοια κλήση Αντίθετα, αν ένα νήμα καλέσει μια διαδικασία για την οποία δεν έχει γίνει ακόμα accept, τότε μπλοκάρει μέχρι η κλήση να γίνει αποδεκτή και να εκτελεστεί Ταυτόχρονος Προγραμματισμός 44 lalis@inf.uth.gr
η P1 περιμένει την P2 η P2 περιμένει την P1 P1 P2 P1 P2 f() accept f() accept f() f() επεξεργασία return επεξεργασία return Ταυτόχρονος Προγραμματισμός 45 lalis@inf.uth.gr
Συντακτικά στοιχεία Συνάρτηση διεπαφής f(in type par,, type par, out type par,, type par); Κλήση f(in 1,,in N,out 1,,out N ) Αναμονή για κλήση και εκτέλεση accept f(in 1,,in N, out 1,,out N ) { /* υλοποίηση συνάρτησης */ Ταυτόχρονος Προγραμματισμός 46 lalis@inf.uth.gr
Αμοιβαίος αποκλεισμός lock: export down(),up(); accept down() { accept up() { Pi: lock.down(); /* κρίσιμο τμήμα */ lock.up(); Ταυτόχρονος Προγραμματισμός 47 lalis@inf.uth.gr
Παραγωγός καταναλωτής buf: export put(in Data d), get(out Data d); Data buf[n]; int in=0,out=0,n=0; accept put(in Data d) { buf[in] = d; in = (in+1)%n; accept get(out Data d) { d = buf[out]; out = (out+1)%n; producer: /* produce d */ buf.put(d); consumer: buf.get(d); /* consume d */ Ταυτόχρονος Προγραμματισμός 48 lalis@inf.uth.gr
Σχόλιο για την προηγούμενη λύση Μέσω των διαδικασιών put και get γίνεται η μεταφορά των δεδομένων προς / από την αποθήκη Η αλλαγή των δεικτών in / out γίνεται έξω από τον κώδικα των διαδικασιών put και get δεν υπάρχει λόγος να παραμείνει μπλοκαρισμένο περισσότερο το νήμα που καλεί αυτές τις διαδικασίες Λόγω της σχετικής σειράς των εντολών accept, η μόνη εφικτή σειρά εκτέλεσης των πράξεων είναι put, get, put, get, put, get κτλ χρησιμοποιείται μόνο μια θέση της αποθήκης Ταυτόχρονος Προγραμματισμός 49 lalis@inf.uth.gr
Αναμονή με επιλογή σε πολλές κλήσεις Στο πνεύμα του CSP select { when C 1 accept foo 1 () Body 1 S 1 when C 2 accept foo 2 () Body 2 S 2 else S N ; Μπλοκάρει μέχρι να κληθεί μια διαδικασία της οποίας η συνθήκη επιλογής είναι αληθής ή να υπάρχει else Το νήμα που καλεί μια διαδικασία μπλοκάρεται μόνο κατά την εκτέλεση του σώματος Body i της, και όχι κατά την εκτέλεση των υπόλοιπων εντολών Si Ταυτόχρονος Προγραμματισμός 50 lalis@inf.uth.gr
Γενικός σηματοφόρος sem: export down(), up(); int val=0; select { when (val>0) accept down() { val--; when (true) accept up() { val++; Ταυτόχρονος Προγραμματισμός 51 lalis@inf.uth.gr
Παραγωγός καταναλωτής buf: export put(in Data d), get(out Data d); data buf[n]; int in=0,out=0,n=0; select { when (n<n) accept put(in Data d) { buf[in] = d; n++; in = (in+1)%n; when (n>0) accept get(out Data d) { d = buf[out]; n--; out = (out+1)%n; producer: /* produce d */ buf.put(d); consumer: buf.get(d); /* consume d */ Ταυτόχρονος Προγραμματισμός 52 lalis@inf.uth.gr