LA SINCRONIZZAZIONE TRA PROCESSI E' più complesso scrivere programmi concorrenti rispetto a programmi sequenziali in quanto non basta essere sicuri della correttezza dei singoli moduli ma è necessario garantire il loro corretto funzionamento per ogni possibile combinazione di interazioni che questi possono avere. Esempio concettuale dove possiamo comprendere gli effetti disastrosi che possono essere generati da errori dipendenti dal tempo o dalla errata sincronizzazione. Quando di usa la programmazione concorrente, per evitare errori dipendenti dal tempo bisogna rispettare 1. 2. 3. 4. condizioni di Bernstein regola di mutua esclusione (proprietà safety) proprietà fairness (correttezza) proprietà liveness (sopravvivenza)
1 condizioni di Bernstein Concetti di dominio e rango intersezione Verificare se le condizioni di Bernstein sono verificate nei 3 esempi sotto riportati
2 regola di mutua esclusione Si ha mutua esclusione quando non più di un processo alla volta può accedere a una risorsa comune. Esempio Si hanno due procedure che operano sulle stesse celle di memoria, quindi due sezioni critiche: Procedura 1: Scrive un nuovo dato nella prima posizione disponibile Procedura 2: Legge l'ultimo dato inserito e lo cancella Procedura 1 (verde) 23 32 12 ultimo = ultimo +1 23 32 12 vet_dati[ultimo] = 87 23 32 12 87 Procedura 2 (rossa) letto = vet_dati[ultimo] 23 32 12 87 vet_dati[ultimo] = 23 32 12 ultimo = ultimo - 1 23 32 12 Se mischio le istruzioni delle due procedure il risultato finale è diverso. I processi non devono interferire tra di loro nell'accesso alle risorse condivise. 23 32 12 ultimo = ultimo +1 23 32 12 letto = vet_dati[ultimo] 23 32 12 vet_dati[ultimo] = 23 32 12 ultimo = ultimo - 1 23 32 12 vet_dati[ultimo] = 87 23 32 87
3 proprietà fairness (correttezza) Garantisce che tutti i processi prima o poi portino a compimento il loro lavoro. Cioè evita la Starvation (blocco individuale). Si verifica quando un processo rimane in attesa di un evento che non accadrà mai. 4 proprietà liveness (sopravvivenza) Garantisce che un processo non aspetti indeterminatamente che una risorsa venga rilasciata. Cioè evita il Deadlock - abbraccio mortale (blocco multiplo). Si verifica a causa di condizioni cicliche. Esempio concettuale Si può verificare se 4 automezzi giungono contemporaneamente da ogni strada all'incrocio e tutti gli autisti si comportano in maniera diligente, cioè danno la precedenza a destra, come previsto dal regolamento della strada, gli automezzi rimangono bloccati senza possibilità di soluzione.
SINCRONIZZAZIONE TRA PROCESSI: SEMAFORI Il programmatore che vuole implementare del codice con esecuzione concorrente ha a disposizione due funzioni P(S) e V(S) che servono per sincronizzare i processi. Semafori di Dijkstra Le due primitive P(S) e V(S) permettono la soluzione di qualsiasi problema di interazione fra processi. P proberen testare (controlla il semaforo e se verde lo diminuisce) V verhogen aumentare (sveglia il processo in attesa e aumenta il semaforo) S semaforo numero intero non negativo S= rosso, S> verde Un processo che vuole accedere a una risorsa condivisa esegue la primitiva P(S) Quando il processo ha terminato di utilizzare la risorsa condivisa esegue la primitiva V(S)
Esempio Si consideri un array da 2 celle, ciascuna di esse destinata a contenere una variabile da condividere, trattasi quindi di una risorsa condivisibile con molteplicità pari a 2. Inizialmente tutte le celle sono vuote, quindi S=2. Se un processo P1 vuole utilizzare una cella, esegue la funzione P: controlla che S = poiché S=2 accede alla risorsa pone S=1 Se un processo P2 vuole utilizzare una cella, esegue la funzione P: controlla che S = poiché S=1 accede alla risorsa pone S= Se un processo P3 vuole utilizzare una cella, esegue la funzione P: controlla che S = poiché S= non accede alla risorsa viene posto nella coda di attesa Se nel frattempo P1 ha terminato di utilizzare la risorsa, esegue la funzione V: controlla se è presente un processo in attesa poiché trova P3 nella coda pone P3 nello stato di pronto pone S=1 A sua volta P3 esegue la funzione P controlla che S = poiché S=1 accede alla risorsa pone S= e così via
Semafori come vincoli di precedenza p.15 I semafori binari (S= rosso e S=1 verde) possono anche essere utilizzati per stabilire vincoli di precedenza sull esecuzione di gruppi di operazioni in processi paralleli.
Problema del rendez-vous p.151
Problemi classici della programmazione concorrente: 1. Produttori e consumatori (p.156) 2. Lettori e scrittori (p.162) 3. Deadlock (p.168) Produttori e consumatori p. 156 Sono presenti due processi il primo, produttore, che scrive un dato in un'area di memoria condivisa il secondo, consumatore, che preleva il dato prodotto dal primo e lo consuma. Vincoli gestiti da semafori: Il produttore deve depositare un dato alla volta non deve scrivere un nuovo dato se il consumatore non ha prelevato il dato precedente. Il consumatore deve prelevare un dato alla volta e solo dopo che sia stato depositato dal produttore non deve prelevare due volte lo stesso dato Lettori e scrittori p. 162 In questo caso più lettori possono contemporaneamente utilizzare un dato senza danneggiarlo. Quindi un lettore può accedere a un dato anche se è già presente un altro lettore, mentre lo scrittore deve accedere in modo esclusivo, cioè quando nessun altro lettore o scrittore sta leggendo o scrivendo un dato.
Deadlock (stallo multiplo) p.168 Il deadlock si verifica quando due processi si ostacolano a vicenda impedendosi reciprocamente di portare a termine il proprio lavoro. Affichè si verifichi un deadlock devono verificarsi contemporaneamente le seguenti 4 condizioni: 1. mutua esclusione: ogni risorsa o è assegnata a un solo processo o è libera 2. assenza di prerilascio: le risorse devono essere rilasciate volontariamente dai processi 3. richieste bloccanti: se il processo non può evolvere senza quella risorsa 4. attesa circolare: ci sono due processi ognuno in attesa di una risorsa occupata dall'altro. Come affrontare il deadlock (p.173) 1. Detection e recovery (rivelarlo e guarirlo) (p.173) 2. Avoidance (evitarlo) (p.174) 3. Prevention (prevenirlo) (p.175) 4. Ignorarlo (p.176) Detection e ricovery (rivelarlo e guarirlo) p.173 Possibili soluzioni: 1. Terminazione dei processi: Si forza la terminazione di un processo alla volta finché non si elimina lo stallo. 2. Prerilascio di una risorsa: Si forza il rilascio di una risorsa 3. Checkpoint/Rollback (salvataggio/ripristino): Si salva periodicamente lo stato dei processi sul disco e in caso di deadlock si ripristina uno o più processi a uno stato precedente. Avoidance (evitarlo) Algoritmo del banchiere (p.174) Ogni processo deve dichiarare il massimo numero di risorse che gli sono necessarie e a ogni nuova richiesta di una risorsa l'algoritmo deve calcolare la quantità di risorse rimanenti, se queste possono soddisfare la massima richiesta di almeno un processo che ancora deve essere servito l'allocazione viene accordata, altrimenti viene negata.
Esempio: Si ha un computer con 3 stampanti su cui vanno in esecuzione 3 processi (P1, P2 3 P3) contemporaneamene. P1 ha bisogno al massimo di una stampante. P2 ha bisogno al massimo di 2 stampanti. P3 ha bisogno al massimo di una stampante. processi in esecuzione contemporaneamente numero massimo di risorse richieste contemporaneamente stato del processo richiesta risorse se accolta richiesta risorsa allocata stato del processo risorse in uso richiesta risorse se accolta richiesta risorsa allocata stato del processo risorse in uso richiesta risorse se accolta richiesta risorsa allocata stato del processo risorse in uso richiesta risorse se accolta richiesta risorsa allocata stato del processo risorse in uso richiesta risorse se accolta richiesta risorsa allocata stato del processo risorse in uso P1 1 in esecuzione 1 sì in esecuzione 1 sì in esecuzione 1 in esecuzione 1 libera risorsa in esecuzione in esecuzione P2 2 in esecuzione no in esecuzione 2 no in attesa in attesa 2 sì in esecuzione 2 in esecuzione 2 libera risorse P3 1 in esecuzione no in esecuzione no in esecuzione 1 no in attesa in attesa 1 sì in esecuzione 1 libera risorsa 3 3-1=2 2 2-2= 2 2-1=1 2 3 3-2=1 1 1-1= 3
Prevention (prevenirlo) (p.175) Si elimina una delle condizioni che provocano un deadlock 1. Mutua esclusione: ogni risorsa o è assegnata a un solo processo o è libera Si elimina permettendo la condivisione di risorse. 2. Assenza di prerilascio: le risorse devono essere rilasciate volontariamente dai processi. Si elimina obbligando un processo a rilasciare le risorse che possiede quando ne richiede un'altra che in quel momento non è disponibile e il processo sarà fatto ripartire solo quando può riguadagnare tutte le risorse che gli servono per evolvere. 3. Richieste bloccanti: se il processo non può evolvere senza quella risorsa Si elimina imponendo che un processo prima di impossessarsi di una risorsa, verifica se sono disponibili tutte le risorse di cui ha bisogno per evolvere, solo in caso positivo se ne impossessa, altrimenti aspetta. (Allocazione totale, hold and wait, filosofi a cena) 4. Attesa circolare: ci sono due processi ognuno in attesa di una risorsa occupata dall'altro. Si elimina attribuendo alle risorse dei valori di priorità e imponendo che a ogni processo possono essere allocate solo risorse di priorità superiore a quelle che già possiede, se invece ha bisogno di una risorsa a priorità inferiore, deve prima rilasciare tutte le risorse con priorità uguale o superiore a quella desiderata. Ignorare lo stallo (p.176) Algoritmo dello struzzo (nascondere la testa sotto la sabbia pensando così di essersi nascosto)). E' troppo costoso mettere in atto le precauzioni sopra descritte, quindi conviene ignorare il problema e affrontarlo solo se si verifica effettuando il reset del sistema. I Monitor p.181 I semafori sono un meccanismo molto potente, ma molto rischioso e difficoltoso per realizzare la sincronizzazione tra processi. Nei linguaggi evoluti di alto livello, come il java, è il compilatore che introduce il codice necessario alla sincronizzazione tra processi, questo meccanismo si chiama monitor. Monitor: Costrutto sintattico utilizzato per effetturae il controllo degli accessi a una risorsa condivisa tra processi concorrenti, che associa un insieme di procedure/funzioni (chiamte entry) a una struttura dati comune a più processi. Le entry sono le sole operazioni permesse su quella struttura. Le entry sono mutuamente esclusive, cioè eseguite da un solo processo alla volta. Per ogni risorsa si crea un monitor che definisce lo stato della risorsa, i processi possono aggiornare lo stato della risorsa mediante le entry.