1 Competizione e cooperazione pag. 88 Cap.3 - Esecuzione concorrente di processi 1
Competizione e cooperazione (3.1, pag. 88) Esistono due tipi di parallelismo: Reale (si realizza con il multiprocessing) Simulato (si realizza con la multiprogrammazione) In entrambi i casi la concorrenza tra processi presenta dei problemi che devono essere affrontati in modo adeguato Nota: i problemi di concorrenza riguardano sia i processi di sistema che quelli degli utenti. In particolare le problematiche riguardano anche le applicazioni multi-threading.
Competizione e cooperazione (3.1, pag. 88) Se due processi (o thread) che operano in parallelo sono completamente indipendenti tra loro non si presenta nessun particolare problema. Ma in molti casi i processi concorrenti possono interagire tra loro con due modalità: COMPETIZIONE E COOPERAZIONE. COMPETIZIONE I processi competono tra loro per l’accesso e l’utilizzo di una risorsa che non possono utilizzare entrambi contemporaneamente. COOPERAZIONE I processi devono cooperare per realizzare un determinato compito. Può essere necessaria la condivisione di risorse e la comunicazione.
2 Mutua esclusione e sincronizzazione pag Cap.3 - Esecuzione concorrente di processi 4
Mutua esclusione e sincronizzazione (3.2, pag ) Il problema della mutua esclusione Esempio N. 1: Due processi operano su un conto corrente, modificando il saldo del conto. Il valore attuale del saldo è (saldo = 1000) Il processo P1 deve effettuare un versamento di 500, cioè: saldo = saldo Il processo P2 deve effettuare un prelievo di 200, cioè: saldo = saldo Se i due processi sono eseguiti in sequenza, in qualsiasi ordine (p1,p2 oppure p2, p1) il risultato finale deve essere: saldo = Ma cosa può accadere in caso di esecuzione concorrente?
Mutua esclusione e sincronizzazione (3.2, pag ) Dobbiamo analizzare il modo in cui le operazioni avvengono nella realtà su un ipotetico processore. P1: (aggiungi 500) …. 1a: MOV AX, saldo 1b: ADD AX, 500 1c: MOV saldo, AX … Nota: i due programmi potrebbero anche utilizzare lo stesso registro, es. AX. Infatti, nel passaggio da un processo all’altro tutti i registri vengono salvati e poi ripristinati. P2: (togli 200) …. 2a: MOV BX, saldo 2b: SUB BX, 200 2c: MOV saldo, BX … Va tutto bene se l’ordine di esecuzione delle istruzioni è: 1a, 1b, 1c, 2a, 2b, 2c oppure 2a, 2b, 2c, 1a, 1b, 1c Ma in ambiente multiprogrammato qualche volta potrebbe accadere ad esempio che l’esecuzione reale avvenga nel seguente ordine: 1a, 1b, 2a, 2b, 1c, 2c Risultato: il saldo finale è 800 anziché 1300 !!!
Mutua esclusione e sincronizzazione (3.2, pag ) Il problema della mutua esclusione Esempio N. 2: Per assegnare i posti liberi in una sala cinematografica si usa un programma. L’assegnazione può essere effettuata da due diverse casse che usano lo stesso programma. Il programma utilizza una variabile NumPostiLiberi (valore iniziale: 100). Il codice per assegnare un posto potrebbe essere il seguente: if (NumPostiLiberi > 0) NumPostiLiberi = NumPostiLiberi – 1; else messaggio “non ci sono più posti liberi”; P1 (assegnazione di un posto alla cassa 1): … 1a: MOV AX, numPosti 1b: CMP AX, 0 1c: JLE postiEsauriti 1d: SUB AX, 1 1e: MOV numPosti, AX … P2 (assegnazione di un posto alla cassa 2): … 2a: MOV AX, numPosti 2b: CMP AX, 0 2c: JLE postiEsauriti 2d: SUB AX, 1 2e: MOV numPosti, AX … Con l’esecuzione concorrente potrebbe accadere che l’esecuzione avvenga nel seguente modo: 1a, 1b, 2a, 2b, 1c, 1d, 1e, 2c, 2d, 2e Se si aveva un solo posto libero (numPosti = 1) entrambi i processi assegnano l’unico posto libero a due diversi spettatori !!!
Mutua esclusione e sincronizzazione (3.2, pag ) In entrambi gli esempi precedenti il problema nasce dal fatto che due processi accedono e modificano un’area di memoria condivisa (la variabile saldo oppure numeroPostiLiberi). In questi casi è necessario che l’accesso alla variabile da parte di un processo escluda l’accesso da parte dell’altro processo per tutta la durata delle operazioni di accesso e modifica. Uno deve escludere l’altro: esclusione reciproca o MUTUA ESCLUSIONE. La sezione di programma da eseguire in mutua esclusione si chiama: SEZIONE CRITICA Programma ContoCorrente …. INIZIO SEZ. CRITICA MOV AX, saldo ADD AX, 500 MOV saldo, AX FINE SEZIONE CRITICA..... Programma PostiLiberiCinema..... INIZIO SEZ. CRITICA MOV AX, numPosti CMP AX, 0 JLE postiEsauriti SUB AX, 1 MOV numPosti, AX FINE SEZIONE CRITICA Il programmatore deve fare molta attenzione a definire correttamente le sezioni critiche del programma altrimenti l’esecuzione concorrente potrebbe non funzionare correttamente. Problema: servono degli strumenti per realizzare concretamente la mutua esclusione. Quando un processo arriva alla sezione critica, se non può accedere deve automaticamente bloccarsi e attendere.
Mutua esclusione e sincronizzazione (3.2, pag. 91) Le porzioni di codice P 0, P 1, … P n,sono eseguite in mutua esclusione quando l’esecuzione di P i può iniziare solo se non è in esecuzione nessuna P j (con i ≠ j) MUTUA ESCLUSIONE Definizione
Mutua esclusione e sincronizzazione (3.2, pag ) Il problema della SINCRONIZZAZIONE Il problema si presenta quando due processi devono cooperare. In certi momenti un processo può andare avanti solo se l’altro ha completato alcune operazioni, altrimenti deve bloccarsi e attendere. Programma A (produttore): do{ ProduciDati(); Inserisci dati in X; }while (ciSonoClienti); Se A opera più velocemente di B, alcuni dati verranno sovrascritti prima che B li abbia prelevati e stampati. Se invece B opera più velocemente di A alcuni dati verranno prelevati due volte e si avranno più stampe uguali. Dunque: A deve bloccarsi prima di inserire i dati in X se B non ha ancora prelevato quelli precedenti. B deve bloccarsi prima di prelevare i dati da X se A non ha ancora inserito un nuovo dato. Esempio: Un processo A elabora e produce i dati relativi a un cliente per volta, inserendoli in un buffer X (area di memoria condivisa). Un altro processo B preleva dal buffer i dati di un cliente e ne fa una stampa. Programma B (consumatore): do{ Preleva dati da X; ElaboraStampaDati(); }while (ciSonoClienti);
Mutua esclusione e sincronizzazione (3.2, pag ) Programma A (produttore): do{ A1:ProduciDati(); A2:Inserisci dati in X; }while (ciSonoClienti); Programma B (consumatore): do{ B1:Preleva dati da X; B2:ElaboraStampaDati(); }while (ciSonoClienti); Esecuzione valida (sincronizzata): A1,A2,B1,B2,A1,A2,B1,B2,…. Esecuzione non valida 1: A1,A2,B1,B2,A1,A2, A1,A2,B1,B2,…(B perde dei dati). Esecuzione non valida 2: A1,A2,B1,B2, B1,B2, A1,A2,… (B rielabora dei dati). Si potrebbe semplicemente evitare il problema scrivendo un normale programma sequenziale: Versione sequenziale Produci-Consuma: do{ ProduciDati(); Scrivi dati in X; Leggi dati da X; ElaboraDati(); }while (ciSonoClienti); Però: a)In un sistema biprocessore la versione concorrente è più efficiente perché A1 e B2 possono avvenire in parallelo (mentre A produce un dato, B elabora quello di prima: i tempi si dimezzano) b)In un sistema multiprogrammato la versione concorrente non porta un vantaggio in termini di efficienza, ma può risultare comunque più chiara, più leggibile, più facile da progettare e correggere.
Mutua esclusione e sincronizzazione (3.2, pag ) Programma A (produttore): do{ ProduciDati(); attendi che buffer X sia vuoto; Inserisci dati in X; comunica che buffer X è pieno; }while (ciSonoClienti); Programma B (consumatore): do{ attendi che buffer X sia pieno; Preleva dati da X; comunica che buffer X è vuoto; ElaboraStampaDati(); }while (ciSonoClienti); Come si può vedere la versione concorrente deve utilizzare dei meccanismi per: 1.Mettere in attesa i processi quando necessario 2.Permettere ai processi di comunicare tra loro Per funzionare correttamente, la versione concorrente dovrebbe essere realizzata secondo lo schema seguente: Come già visto per la mutua esclusione, anche per la sincronizzazione sono necessari degli strumenti di programmazione nuovi, non disponibili nella tradizionale programmazione sequenziale.
Mutua esclusione e sincronizzazione (3.2, pag. 96) Prima di vedere quali possono essere le caratteristiche di questi strumenti, vediamo i problemi che potranno sorgere dall’uso di questi strumenti. Ne esistono in particolare due: STARVATION e DEADLOCK Per comprendere cosa può accadere e perché consideriamo la necessità di regolamentare l’accesso a risorse condivise. Nota: un’area di memoria condivisa è un caso particolare, ma risorse condivise possono essere anche unità periferiche, lettori CD, modem, ecc. Processo A richiede risorsa R 1. R è libera. Il processo A può acquisire la risorsa R. Mentre A usa R nessun altro processo potrà usarla, ma dovrà aspettare che A la rilasci. A R A R 2. R è occupata. Il processo A deve mettersi in attesa che la risorsa R sia libera. Il processo A ha ottenuto la risorsa R Il processo A aspetta di avere la risorsa R Ci sono due possibilità: la risorsa R può essere libera o occupata.
Mutua esclusione e sincronizzazione (3.2, pag. 96) 1° problema possibile: STARVATION Il problema della starvation è già noto per l’utilizzo del processore. La stessa cosa si può verificare per una qualsiasi risorsa. 1. B sta utilizzando R. A e C attendono che R sia libera A R B C A R C 2. R si libera, ma viene assegnata a C. A rimane in attesa. A R D 3. Quando anche C termina, arriva un altro processo D che richiede la risorsa R. Il sistema la assegna a D. A rimane in attesa … Se nel frattempo arrivano E,F,G, ecc. a richiedere R e il sistema li fa passare prima, A rimane in attesa … Il processo A attende che la risorsa R sia libera, ma quando R è libera il sistema la assegna sempre ad un altro processo in attesa E F G
Mutua esclusione e sincronizzazione (3.2, pag. 96) 2° problema possibile: DEADLOCK (blocco mortale) o STALLO Consideriamo due programmi P1 e P2 che richiedono le risorse X e Y. P1: …. 1a: Richiede X (lock X) Usa X 1b: Richiede Y (lock Y) Usa Y 1c: Rilascia Y (unlock Y) 1d: Rilascia X (unlock X).... P2: …. 2a: Richiede Y (lock Y) Usa X 2b: Richiede X (lock X) Usa Y 2c: Rilascia X (unlock X) 2d: Rilascia Y (unlock Y) …. In ambiente concorrente può accadere che l’esecuzione avvenga nel seguente ordine: 1a,2a,1b,2b. Cosa accade? P1 XY 1: P1 acquisisce X P1 XY P2 2. P2 acquisisce Y 3. P1 richiede Y, si mette in attesa P1 X Y P2 P1 XY P2 3. P2 richiede X, si mette in attesa
Mutua esclusione e sincronizzazione (3.2, pag. 96) DEADLOCK Consideriamo due programmi P1 e P2 che richiedono le risorse X e Y. P1: …. 1a: Richiede X Usa X 1b: Richiede Y Usa Y 1c: Rilascia Y 1d: Rilascia X.... P2: …. 2a: Richiede Y Usa X 2b: Richiede X Usa Y 2c: Rilascia X 2d: Rilascia Y … In ambiente concorrente può accadere che l’esecuzione avvenga nel seguente ordine: 1a,2a,1b,2b. Cosa accade? P1 X P2 Y P1 richiede X X è libera e viene assegnata a P1 P2 richiede Y Y è libera e viene assegnata a P2 P1 richiede Y Y è occupata da P2. P1 si ferma e si mette in attesa P2 richiede X X è occupata da P1. P2 si ferma e si mette in attesa A questo punto: P1 è bloccato perché aspetta che P2 rilasci Y Ma P2 è fermo anch’esso perché aspetta che P1 rilasci X. La situazione è un DEADLOCK (STALLO). Entrambi i processi rimarranno bloccati per sempre!
Mutua esclusione e sincronizzazione (3.2, pag. 96) Il Deadlock può coinvolgere anche più di due processi, come nel seguente esempio. Processo A: … A1: lock(Lettore CD) … A2: lock(Masterizzatore) … A3: lock(Nastro) … A4: unlock(Nastro) A5: unlock(Masterizzatore) A6: unlock(Lettore CD) … A Lettore CD B Processo B: … B1: lock(Masterizzatore) … B2: lock(Nastro) … B3: lock(Lettore CD) … B4: unlock(Nastro) B5: unlock(Masterizzatore) B6: unlock(Lettore CD) … Processo C: … C1: lock(Nastro) … C2: lock(Lettore CD) … C3: lock(Masterizzatore) … C4: unlock(Nastro) C5: unlock(Masterizzatore) C6: unlock(Lettore CD) … Masterizz. Nastro C Se siamo particolarmente sfortunati l’esecuzione parallela potrebbe avvenire nel seguente ordine: A1 – B1 – C1 – A2 – B2 – C2 Nota: Lock = richiesta risorsa (se è occupata il processo si mette in attesa) Unlock = rilascia risorsa A2 A1 B1 B2 C1 C2 A1: A acquisisce il Lettore CD B1: B acquisisce il Masterizzatore C1: C acquisisce il Nastro A2: A si mette in attesa per il Masterizzatore B2: B si mette in attesa per il Nastro C2: C si mette in attesa per il lettore CD I tre processi A, B, C sono bloccati per sempre: DEADLOCK
Mutua esclusione e sincronizzazione (3.3, pag. 96) SOLUZIONI PER GESTIRE LA MUTUA ESCLUSIONE Ogni possibile soluzione del problema della mutua esclusione deve rispettare le seguenti condizioni: 1.Garantire che un solo processo per volta sia all’interno della sezione critica 2.Un processo che è fuori dalla sezione critica non deve impedire a un altro processo di accedere 3.La soluzione deve funzionare indipendentemente dalla velocità di esecuzione di ciascun processo. Ci sono tre categorie di soluzioni: 1.Software 2.Hardware 3.Costrutti speciali (semafori, monitor, regioni critiche)
Mutua esclusione e sincronizzazione (3.3, pag. 96) MUTUA ESCLUSIONE: soluzioni software La soluzione è solo in apparenza semplice. Esempio: si potrebbe pensare di usare una variabile booleana Occupata (condivisa), che indica se la sezione critica è libera (Occupata=0) oppure è già occupata da un processo (Occupata=1). Programma P1: … while (Occupata){ //non fa niente } Occupata=1; Sezione critica; Occupata = 0; … Programma P2: … while (Occupata){ //non fa niente } Occupata=1; Sezione critica; Occupata = 0; … In realtà questa soluzione non funziona. Potrebbe accadere che entrambi i processi accedano alla sezione critica. Perché? Inizializzazione: Occupata = 0; Occupata 1 0 Occupata=1 Occupata=0 Sezione Critica Problema
Mutua esclusione e sincronizzazione (3.3, pag. 99) La prima soluzione software valida per il problema della mutua esclusione è stata trovata da Dekker (1965). L’algoritmo è abbastanza complesso. 16 anni dopo (1981) Peterson trova una soluzione più semplice. Gli algoritmi di Dekker e Peterson sono interessanti dal punto di vista teorico. Presentano però alcuni problemi. In particolare: 1.L’attesa per l’ingresso in sezione critica tiene impegnata la CPU (busy waiting) 2.La generalizzazione ad N processi è molto complessa.
Mutua esclusione e sincronizzazione (3.4, pag ) MUTUA ESCLUSIONE: soluzioni hardware Soluzione 1: Disabilitare le interruzioni Programma P1: … Disabilita Interrupt Sezione critica; Abilita Interrupt; … Programma P2: … Disabilita Interrupt Sezione critica; Abilita Interrupt; … Soluzione semplice, funziona anche con N processi concorrenti Soluzione non valida per sistemi multiprocessore Rischiosa nei sistemi multiprogrammati (se il programma si blocca nella sezione critica le interruzioni rimangono disabilitate) Durante la sezione critica il sistema funziona come se fosse in monoprogrammazione (se la sezione critica è breve, non è un problema grave, altrimenti sì). Problemi
Mutua esclusione e sincronizzazione (3.4, pag ) MUTUA ESCLUSIONE: soluzioni hardware Soluzione 2: Istruzioni macchina speciali Esempio: Istruzione Test And Set Lock (TSL) TSL reg, val Trasferisce il valore val nel registro reg, impostando val a 1. Il valore presente in reg può poi essere testato. Programma P1: … cicloAttesa: TSL AX, occupata CMP AX, 0 JNE cicloAttesa Sezione critica; MOV occupata, 1 … Programma P2: … cicloAttesa: TSL AX, occupata CMP AX, 0 JNE cicloAttesa Sezione critica; MOV occupata, 1 … Esempio: usiamo una variabile di memoria condivisa, chiamata occupata, inizializzata a 0. Questa soluzione garantisce la mutua esclusione. Solo un processo per volta può entrare nella sezione critica. 1.L’attesa impegna la CPU (busy waiting) 2.Se più processi sono in attesa non si tiene conto dell’ordine di arrivo Nota: l’esempio usa uno pseudo-assembly simile a quello visto per l’8086. Nei processori Intel 386 (e successivi) c’è l’istruzione BTS (Bit Test and Set). Problemi In definitiva, la soluzione è accettabile solo se la sezione critica è molto breve e non soggetta a possibili malfunzionamenti. Non dovrebbe essere usata normalmente dal programmatore.
Mutua esclusione e sincronizzazione (3.5, pag. 104) MUTUA ESCLUSIONE : la prima vera soluzione specializzata Un Semaforo S è un oggetto costituito da: Una variabile intera C o Contatore Una coda di processi associata al semaforo Se il valore di Contatore è negativo o nullo il semaforo indica occupato. Il valore indica il numero di processi in coda di attesa. Se il valore di Contatore è positivo il semaforo indica libero. Il valore indica quanti processi possono accedere. Per la mutua esclusione il valore iniziale deve essere 1 (semaforo binario). -3P1P2P3 Occupato. 3 processi in coda attendono 1 Libero. Può entrare un processo. i SEMAFORI (E.W.Dijkstra) 4 Libero. Possono accedere 4 processi. 0 Occupato. Nessun processo attende in coda. Nota: un Semaforo che ha il contatore con valore maggiore di 1 si chiama Semaforo Contatore. Si utilizza ad esempio per accedere a risorse multiple.
Mutua esclusione e sincronizzazione (3.5, pag ) Si usano due primitive (procedure ad hoc) per operare su un semaforo: Wait (o P) Signal (o V) SEMAFORI di Dijkstra Note: L’operazione BloccaProcesso() fa passare il processo corrente dallo stato Running allo stato Waiting L’operazione RiattivaProcesso(P) fa passare il processo P dallo stato Waiting allo stato Ready Il passaggio dei parametri avviene come se fosse un riferimento a un oggetto (come in java) P e V sono i nomi originali dati da Dijkstra alle primitive Wait e Signal IMPORTANTE: Tutto il codice della primitiva Wait e della primitiva Signal deve essere eseguito come una sezione critica, cioè in mutua esclusione. L’implementazione della suddetta mutua esclusione avviene utilizzando uno dei metodi hardware visti in precedenza (disabilitazione interruzioni, istruzione TestAndSet, …). Note: click qui Importante: click qui Procedura Wait(Semaforo S) { S.conta = S.conta – 1; if (S.conta < 0){ InserisciProcessoInCoda(S); BloccaProcesso(); } P Procedura Signal(Semaforo S) { if (S.conta < 0){ Processo P = EstraiProcessoDaCoda(S); RiattivaProcesso (P); } S.conta = S.conta + 1; } V
Mutua esclusione e sincronizzazione (3.5, pag ) Realizzazione della Mutua Esclusione mediante Semafori Per definire una sezione critica si utilizza un Semaforo binario MutEx, inizializzato a 1. La sezione critica deve essere delimitata da una Wait(Mutex) all’inizio e Signal(Mutex) alla fine Programma P1: … Wait(mutex); Sezione critica; Signal(mutex) ; … Programma P2: … Wait(mutex); Sezione critica; Signal(mutex); … Nota: Il sistema funziona anche con N processi Semaforo mutex(Count=1); //var condivisa di tipo Semaforo Procedura Wait(Semaforo S) { S.conta = S.conta – 1; if (S.conta < 0){ InserisciProcInCoda(S); BloccaProcesso(); } Procedura Signal(Semaforo S) { if (S.conta < 0){ P = EstraiProcDaCoda(S); RiattivaProcesso (P); } S.conta = S.conta + 1; } Programma Pn: … Wait(mutex); Sezione critica; Signal(mutex); … ……. Quando esce dalla sezione critica il processo esegue una Signal sul semaforo. Se il contatore era negativo vuol dire che c’era qualche processo in coda: il primo processo in coda viene riattivato. In ogni caso si incrementa il contatore. Prima di entrare in sezione critica il processo esegue una Wait sul semaforo, decrementando così il contatore. Se il contatore era a 1 (accesso libero) il processo entra. Altrimenti (se era a 0 o negativo) viene messo in coda e sospeso.
Mutua esclusione e sincronizzazione (3.5, pag. 107) Uso dei Semafori per la Sincronizzazione Oltre che per la mutua esclusione, il semaforo è uno strumento che consente anche di gestire il problema della sincronizzazione tra processi. Per sincronizzare due processi su un certo evento si associa all’evento un semaforo S. Un processo può segnalare che è avvenuto un evento eseguendo una Signal (S). Un processo che per proseguire deve aspettare che sia avvenuto l’evento esegue una Wait(S). Se l’evento non è avvenuto il contatore del semaforo vale 0. ovvero: Due piccioni (mutua esclusione e sincronizzazione) con una fava (il semaforo) Se l’evento è avvenuto il contatore vale 1
Programma A (produttore): do{ ProduciDato(); Wait(DatoPrelevato); Inserisci dato in X; Signal(DatoCaricato); }while (ciSonoClienti); Programma B (consumatore): do{ Wait(DatoCaricato) Preleva dato da X; Signal(DatoPrelevato); ElaboraDato; }while (ciSonoClienti); Uso dei Semafori per la Sincronizzazione Esempio: problema del PRODUTTORE / CONSUMATORE Semaforo DatoCaricato (Count=0); //per segnalare l’avvenuto caricamento di un dato Utilizziamo due semafori: Semaforo DatoPrelevato (Count=1); //per segnalare l’avvenuto prelevamento di un dato Utilizziamo un’area di memoria condivisa X (Buffer). Il Produttore produce un dato. Prima di caricarlo nel buffer X aspetta che il dato precedente sia stato prelevato. Dopo avere caricato il dato nel buffer X, comunica che l’evento è avvenuto. Il Consumatore aspetta che un dato sia stato caricato. Dopo avere prelevato il dato comunica che il buffer è libero e può essere caricato un nuovo dato. Rientra nel ciclo, produce un nuovo dato e aspetta che quello precedente sia stato prelevato. Intanto elabora il dato. Poi rientra nel ciclo e aspetta che sia stato caricato un nuovo dato. e così via… Mutua esclusione e sincronizzazione (3.6, pag. 109)
Mutua esclusione e sincronizzazione (3.7, saltare) Il semaforo è considerato un meccanismo a basso livello. Costrutti di alto livello e considerazioni generali sulla concorrenza Esistono dei costrutti a livello più alto che consentono di gestire la mutua esclusione e la sincronizzazione in modo più controllato (realizzati internamente mediante semafori). Alcuni di questi costrutti sono: Monitor e Regioni critiche condizionali. Linguaggi di programmazione avanzati (come Java) mettono a disposizione diversi tipi di strumenti per gestire questi problemi nel multi-threading. In ogni caso la programmazione concorrente richiede molta accortezza per evitare i potenziali problemi di deadlock o di mancata sincronizzazione. Le situazioni di errore possono dipendere da un concatenarsi di eventi particolare che può avvenire per caso e che non necessariamente si ripete ogni volta. Ad esempio, un deadlock (che può causare il totale blocco di un’applicazione o dell’intero sistema) potrebbe avvenire anche solo una volta ogni tanto. Diventa quindi difficile individuare e risolvere il problema.
Mutua esclusione e sincronizzazione (3.8, pag. 128, Lo stallo o deadlock) Lo Stallo o DEADLOCK Lo stallo (o deadlock) è un problema che si può verificare nella programmazione concorrente. Si tratta di un blocco che coinvolge un gruppo di due o più processi in competizione per due o più risorse. Risorse prerilasciabili e risorse non prerilasciabili. Le risorse prerilasciabili possono essere tolte a un processo senza comprometterne il funzionamento. Esempi: CPU o memoria centrale possono essere temporaneamente tolte a un processo e date a un altro. Le risorse non prerilasciabili non possono essere tolte a un processo perché si altererebbe il funzionamento del processo. Esempi: Nastri, masterizzatore, lettore CD o DVD. Il DEADLOCK si può avere solo con risorse non pre-rilasciabili. Quando un processo ha acquisito la risorsa, un altro processo che ne ha bisogno deve aspettare necessariamente che il primo abbia finito. Il blocco si verifica quando ciascun processo possiede qualche risorsa ma per proseguire deve aspettare che si liberi una risorsa posseduta da un altro processo. Se l’attesa è circolare si ha il DEADLOCK.
Mutua esclusione e sincronizzazione (3.8, pag. 129, Lo stallo o deadlock) Condizioni che portano allo stallo (deadlock) Mutua esclusione: le risorse non sono utilizzabili contemporaneamente da due processi. Possesso e attesa: i processi acquisiscono le risorse che gli servono. Se una risorsa non è disponibile il processo si blocca in attesa. Le risorse non sono pre-rilasciabili. Si verifica una situazione di attesa circolare (P1 attende una risorsa che sta usando P2, P2 attende una risorsa che sta usando P3, …, Pn attende una risorsa che sta usando P1). P2 possiede la risorsa B, ma attende la risorsa C P1 possiede la risorsa A, ma attende la risorsa B P1 P2 P4 P3 A B D C P3 possiede la risorsa C, ma attende la risorsa D P4 possiede la risorsa D, ma attende la risorsa A Esempio Grafo di allocazione delle risorse Rappresenta in forma grafica la situazione di allocazione delle risorse. La presenza di un ciclo nel grafo indica una situazione di DEADLOCK.
Mutua esclusione e sincronizzazione (3.8, pag , Lo stallo o deadlock) Grafo di allocazione delle risorse P X Processo P attende risorsa X P X Processo P possiede risorsa X Una risorsa può anche essere multipla (doppia, tripla, ecc.) P Q P Y Q R X In caso di risorse multiple, per vedere se c’è un deadlock bisogna prima fare la riduzione del grafo. Se dopo la riduzione rimane un ciclo allora c’è DEADLOCK.
Mutua esclusione e sincronizzazione (3.8, pag , Lo stallo o deadlock) Tecniche per prevenire il deadlock Negare la mutua esclusione Impossibile se ci sono risorse non condivisibili Negare possesso e attesa. Se un processo ha bisogno di più risorse, prima di acquisirne una deve aspettare che siano tutte libere Sottoutilizzazione delle risorse Impossibilità a volte di conoscere in anticipo quali risorse serviranno Prerilasciare risorse Se un processo non può proseguire deve rilasciare tutte le risorse che aveva. Impossibile con certe risorse (non prerilasciabili). Pensare a un masterizzatore. Impedire attesa circolare Creare un ordinamento delle risorse e imporre che l’acquisizione avvenga in modo ordinato. Uso inefficiente delle risorse In sostanza non esistono tecniche di prevenzione ottimali
Mutua esclusione e sincronizzazione (3.8, pag , Lo stallo o deadlock) E allora che si fa per il deadlock? Ignorare il problema Il S.O. non fa niente per prevenire lo stallo e non usa nessuna tecnica per scoprire se si è verificato. Il deadlock non dovrebbe accadere (se i programmi sono tutti fatti bene). Ma se succede non si può fare niente. L’utente o il sistemista può essere costretto a terminare uno o più processi oppure riavviare il sistema. Il Sistema cerca di scoprire la presenza di un deadlock e recuperare la situazione. Bisogna implementare algoritmi che costruiscono il grafo delle risorse e ricercano la presenza di cicli (molto oneroso). Una volta individuato un deadlock si terminano i processi coinvolti fino a che il deadlock non si risolve. Le strategie effettivamente usate dai sistemi operativi sono due