Algoritmi concorrenti Lezione n°15 Algoritmi Avanzati a.a.2013/2014 Prof.ssa Rossella Petreschi Sistemi concorrenti Sistemi paralleli MIMD asincroni con memoria condivisa Ogni processore esegue un proprio programma sequenziale (processo), che può essere uguale o diverso da quello degli altri processori. I processi scambiano i dati attraverso la memoria comune e, poiché i processi sono asincroni, bisogna accedere alle variabili comuni in modo mutuamente esclusivo. Mutua esclusione Per garantire la mutua esclusione su gli accessi, si usano le primitive di sincronizzazione: lock ed unlock. Se un processo P vuole acquisire una variabile x, si deve verificare se x è libera o già acquisita da un altro processore Q. Quando il processo P vuole acquisire x: • se x è libera, P deve eseguire lock(x). In tal caso nessun altro processore potrà accedere ad x finché P non la rilascerà, ovvero finché P non eseguirà unlock(x). • se x è già acquisita da un altro processore Q, allora P deve rimanere in attesa finché Q non eseguirà unlock(x). Stallo Associamo delle parentesi alle operazioni di lock e unlock: lock(x) (parentesi aperta), unlock(x) (parentesi chiusa). Chiamiamo sezione critica la porzione di codice comprendente tutte le istruzioni necessarie per l’elaborazione della variabile x : la sezione critica sarà quindi compresa fra una parentesi aperta (lock(x)) e una parentesi chiusa (unlock(x)). Data la natura asincrona dei sistemi concorrenti, si può creare un fenomeno di stallo quando uno o più processi rimangono in attesa per un tempo “infinito”. Esempio: P:… lock(x)…lock(y)…unlock(x)…unlock(y) Q:…lock(y)…lock(x)…unlock(x)…unlock(y) Analisi delle prestazioni Una analisi teorica delle prestazioni di una macchina concorrente si può discostare molto dalle prestazioni reali perché bisogna tener conto delle attese dovute sia a sincronizzazione su variabili condivise sia a conflitti sul circuito di indirizzamento della memoria per accessi a variabili non condivise. Misurando le prestazioni tramite speed-up si può arrivare così al PARADOSSO: aumentando il numero di processi si peggiora lo speed-up. Ha senso misurare le prestazioni di una macchina concorrente solo in modo sperimentale su macchine reali, variando il numero di processori. Calcolo del massimo Per calcolare il massimo in modo concorrente, consideriamo che nel programma principale sia opportunamente inizializzata una variabile massimo globale (max-glo) e che poi ci sia un ciclo parallelo che faccia cercare ad ogni processore, nel sottoinsieme di n/p valori ad esso assegnati, il proprio massimo locale(max-lo). For all 1 ≤ i ≤ p concurdo MAX(i) In Max(i), ogni processore, dopo aver calcolato sequenzialmente il proprio max-lo(i), chiede l’esclusività della variabile max-glo (lock(max-glo)) per poterla confrontare con il proprio max-lo ed eventualmente aggiornarla. Poi max-glo verrà rilasciata (lock(max-glo)). Lock(max-glo); If max-lo(i) > max-glo then max-glo =max-lo(i); Unlock(max-glo). La complessità teorica di questo algoritmo è O(n/p +p) che diventa O (√n) se p=√n. In tal caso anche lo speed-up sarà O(√n). Cammini minimi Input: G (V,E), grafo pesato con pesi interi su gli archi e r nodo di V. Output: per ogni nodo v di V, trovare il cammino di costo minimo da r a v. Condizione: non debbono esistere in G cicli di lunghezza negativa. Soluzione ammissibile: albero di copertura radicato in r (che quindi include un cammino da r ad ogni altro nodo di V). Soluzione ottima (teorema di Bellman): una soluzione ammissibile T è ottima sse vale che: per ogni arco (u,v) di T: dv = du + cu,v per ogni arco (u,v) di G: dv ≤ du + cu,v Procedura concorrente per cammini minimi Input: G (V,E), grafo pesato con pesi interi su gli archi e r nodo di V. Output: per ogni nodo v di V, trovare il cammino di costo minimo da r a v. Procedura Cammini-minimi (G,r) begin for all 1 ≤ i ≤ p concurdo Inizializza(i); poni r in S; poni stop a false; (S e stop sono variabili globali) for all 1 ≤ i ≤ p concurdo Ricerca(i) end; Inizializza(i) begin for v = 1 to n by p do poni dv e pv a 0; else poni dv ad infinito e pv ad r; end; Procedura Ricerca Ricerca(i) Ogni processore pone a false la sua variabile locale “attesa”. La variabile globale “stop” si ottiene dall’end di tutte le attese (i). Attesa(i) è posta a true quando S è pari all’insieme vuoto, ovvero quando nessun processore trova più vertici su cui lavorare. Ogni processore lavora in esclusiva su S per prelevare il vertice u, se ancora c’è, per cui calcolare il cammino minimo Begin poni a false attesa(i) while not stop do lock(S); if S è vuoto then poni a true attesa(i); if i=1 then stop = and attesa(i), per ogni i; unlock(s) else estrai u dalla testa di S; poni a false attesa(i); unlock(s) segue For each (u,v) in E do… Ora l’algoritmo applica il teorema di Bellman: for each (u,v) di E do dist. =du + cu,v; lock (dv) if dist.a < dv then poni dv = dist.a e pv= u; unlock(dv); lock (S); inserisci v in S se già non c’è; unlock (S); else unlock(dv); chiudi tutto.