Deadlock - condizioni, prevenzione, evitamento e rilevamento
In questa pagina 9
Quando i processi condividono risorse e le richiedono in modo concorrente (Semafori, monitor e problemi classici di sincronizzazioneUn semaforo è un intero accessibile solo con due operazioni atomiche, wait (P: aspetta finché S > 0 e decrementa) e signal (V, in POSIX sem_post: incrementa); il semaforo contatore ha valori illimitati, quello binario vale 0 o 1; con valore iniziale 1 realizza la mutua esclusione, con valore iniziale 0 impone un ordine di esecuzione (S1 prima di S2); senza attesa attiva ogni semaforo ha una coda di processi sospesi (sleep e wakeup); i semafori si usano male con facilità (ordine sbagliato, omissioni); il monitor è un tipo di dato astratto in cui un solo processo alla volta è attivo e le variabili condizione x.wait()/x.signal() sospendono e risvegliano (in POSIX mutex più pthread_cond_t); le anomalie di liveness sono deadlock, starvation e inversione di priorità; i problemi classici sono produttore-consumatore a buffer limitato (mutex, vuote = n, piene = 0; l'ordine delle wait è importante), lettori-scrittori (rw_mutex, mutex, read_count; i lettori possono far morire di fame gli scrittori) e filosofi a cena (la soluzione ingenua con un semaforo per bacchetta va in deadlock; si evita limitando a n-1 filosofi, rompendo la simmetria o ordinando le risorse), con soluzioni in C (POSIX) e in Go (canali e sync).Semafori, monitor e problemi classici di sincronizzazione →) può accadere che ognuno aspetti una risorsa tenuta da un altro, e che nessuno possa più andare avanti. Questa nota definisce lo stallo (deadlock), lo caratterizza con il grafo di allocazione delle risorse, e presenta i quattro approcci: prevenire, evitare (con l'algoritmo del banchiere), rilevare e ripristinare, ignorare. Gli esercizi numerici (banchiere e grafi) sono nell'elenco in fondo.
Modello di sistema
Un sistema ha risorse di vari tipi (cicli di CPU, spazio di memoria, file, dispositivi di I/O, ma anche mutex e semafori), e ogni tipo ha una o più istanze identiche. Ogni processo usa una risorsa in tre passi: richiesta (se non è disponibile, aspetta), uso, rilascio.
Un primo esempio con due semafori e due thread:
T1: wait(S1) wait(S2) ... T2: wait(S2) wait(S1) ...Se ottiene , poi ottiene , poi chiede e chiede , nessuno può continuare: è un deadlock. Si vede in pratica nel laboratorio 6 (Esercizio - Deadlock con semafori e thread (laboratorio 6)).
Definizione (deadlock). Un insieme di processi è in deadlock (stallo, blocco critico) se ogni processo dell'insieme sta aspettando un evento (il rilascio di una risorsa) che può essere causato solo da un altro processo dello stesso insieme.
Caratterizzazione: le quattro condizioni
Un deadlock può verificarsi solo se valgono contemporaneamente quattro condizioni (condizioni di Coffman):
| condizione | significato |
|---|---|
| mutua esclusione | almeno una risorsa è non condivisibile: un solo processo alla volta la usa |
| hold and wait (possesso e attesa) | un processo che possiede almeno una risorsa aspetta di ottenerne altre, possedute da altri |
| no preemption (assenza di prelazione) | una risorsa può essere rilasciata solo volontariamente dal processo che la possiede, dopo aver finito |
| attesa circolare (circular wait) | esiste un insieme tale che aspetta una risorsa di , una di , ..., una di , e una di |
Grafo di allocazione delle risorse
Definizione (grafo di allocazione delle risorse). È un grafo diretto con insieme di vertici , dove sono i processi (nodi circolari) e i tipi di risorse (nodi rettangolari, con un punto per ogni istanza). Un arco di richiesta dice che ha chiesto un'istanza di e la attende; un arco di assegnazione dice che un'istanza di è assegnata a .
Per i grafi diretti, i cicli e la loro ricerca si rimanda a Grafi diretti e ordinamento topologicoGrafi diretti (archi orientati, grado entrante e uscente, cammini e cicli diretti), visite su grafi diretti e tipi di archi; DAG; ordinamento topologico con l'algoritmo basato sui gradi entranti e con la DFS (reverse postorder), correttezza e costo Theta(n+m); esempio svolto.Grafi diretti e ordinamento topologico →, Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà → e Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →. Come si legge un grafo:
| caso | conclusione |
|---|---|
| il grafo non ha cicli | nessun deadlock |
| ha un ciclo e ogni risorsa ha una sola istanza | deadlock (il ciclo è necessario e sufficiente) |
| ha un ciclo e alcune risorse hanno più istanze | deadlock possibile, non certo (il ciclo è necessario ma non sufficiente) |
Esempio: sistema con 3 processi e 4 tipi di risorse (: 1 istanza, : 2, : 1, : 3). possiede un'istanza di e aspetta ; possiede e un'istanza di e aspetta ; possiede . Gli archi sono , , , , , : nessun ciclo, quindi niente stallo ( finisce, libera , prosegue, e così via). Se anche chiedesse (arco ) si avrebbe il ciclo e i processi , , sarebbero in stallo; se invece un ciclo passa per una risorsa con più istanze, può esserci un'istanza posseduta da un processo fuori dal ciclo che prima o poi la rilascerà, e il ciclo si scioglie.
Come trattare i deadlock
- Garantire che il sistema non entri mai in deadlock: prevenzione (negare almeno una delle quattro condizioni) oppure evitamento (usare informazioni in più per scegliere le assegnazioni).
- Lasciare entrare il sistema in deadlock e poi rilevarlo e ripristinarlo.
- Ignorare il problema, fingendo che non capiti (è la scelta di Linux e Windows: il costo di prevenire è maggiore di quello di un riavvio raro, e la responsabilità è dei programmatori).
Prevenzione
Si invalida una delle quattro condizioni.
| condizione negata | come | svantaggi |
|---|---|---|
| mutua esclusione | non serve per le risorse condivisibili (file in sola lettura); non si può negare per quelle non condivisibili (una stampante) | |
| hold and wait | un processo chiede una risorsa solo quando non ne possiede nessuna, oppure se le fa assegnare tutte all'inizio | bassa utilizzazione delle risorse, possibile starvation |
| no preemption | se un processo chiede una risorsa non disponibile, rilascia tutte quelle che possiede (che finiscono nella sua lista d'attesa); riparte solo quando riottiene le vecchie più la nuova | adatto a risorse il cui stato si salva (registri, memoria), non a una stampante o un mutex |
| attesa circolare | si impone un ordinamento totale dei tipi di risorsa e si chiede ogni processo di richiedere le risorse in ordine crescente (, poi , ...) | sceglie l'ordine ma non lo fa rispettare da solo |
Perché l'ordinamento nega l'attesa circolare? Un'attesa circolare richiederebbe che l'ultimo processo chieda una risorsa del primo, cioè con un indice minore di quella che già possiede: contro la regola. È la soluzione dei filosofi a cena con l'indice minore per primo (Semafori, monitor e problemi classici di sincronizzazioneUn semaforo è un intero accessibile solo con due operazioni atomiche, wait (P: aspetta finché S > 0 e decrementa) e signal (V, in POSIX sem_post: incrementa); il semaforo contatore ha valori illimitati, quello binario vale 0 o 1; con valore iniziale 1 realizza la mutua esclusione, con valore iniziale 0 impone un ordine di esecuzione (S1 prima di S2); senza attesa attiva ogni semaforo ha una coda di processi sospesi (sleep e wakeup); i semafori si usano male con facilità (ordine sbagliato, omissioni); il monitor è un tipo di dato astratto in cui un solo processo alla volta è attivo e le variabili condizione x.wait()/x.signal() sospendono e risvegliano (in POSIX mutex più pthread_cond_t); le anomalie di liveness sono deadlock, starvation e inversione di priorità; i problemi classici sono produttore-consumatore a buffer limitato (mutex, vuote = n, piene = 0; l'ordine delle wait è importante), lettori-scrittori (rw_mutex, mutex, read_count; i lettori possono far morire di fame gli scrittori) e filosofi a cena (la soluzione ingenua con un semaforo per bacchetta va in deadlock; si evita limitando a n-1 filosofi, rompendo la simmetria o ordinando le risorse), con soluzioni in C (POSIX) e in Go (canali e sync).Semafori, monitor e problemi classici di sincronizzazione →) e la correzione dei laboratori di deadlock1.c.
Evitamento
L'evitamento richiede informazioni a priori: il modello più semplice vuole che ogni processo dichiari il numero massimo di risorse di ciascun tipo che userà. Lo stato di allocazione è definito dalle risorse disponibili, da quelle assegnate e dalle richieste massime. Un algoritmo di evitamento esamina dinamicamente lo stato per garantire che non si possa mai creare un'attesa circolare.
Stato sicuro
Definizione (stato sicuro). Uno stato è sicuro se esiste una sequenza sicura di tutti i processi tale che, per ogni , le risorse che può ancora chiedere si possono soddisfare con le risorse disponibili ora più quelle possedute da tutti i con .
Cioè: se non ha subito ciò che gli serve, aspetta che i precedenti finiscano; allora ottiene le risorse, esegue, restituisce tutto e termina; poi tocca a , e così via.
- stato sicuro nessun deadlock;
- stato non sicuro deadlock possibile (non certo);
- l'evitamento garantisce che il sistema non entri mai in uno stato non sicuro.
Evitamento con il grafo (una sola istanza per risorsa)
Si aggiunge l'arco di rivendicazione (claim edge) , tratteggiato: potrebbe chiedere in futuro. Quando la chiede diventa arco di richiesta; quando gliela si assegna diventa arco di assegnazione; quando la rilascia torna di rivendicazione. Le risorse vanno dichiarate in anticipo. Una richiesta si può soddisfare solo se la conversione dell'arco di richiesta in arco di assegnazione non crea un ciclo (contando anche gli archi di rivendicazione): altrimenti il processo attende.
L'algoritmo del banchiere (istanze multiple)
Si usa quando i tipi hanno più istanze. Ogni processo dichiara in anticipo il massimo; quando chiede una risorsa potrebbe dover aspettare; quando ottiene tutto deve restituirlo in tempo finito. Con processi e tipi di risorse:
| struttura | dimensioni | significato |
|---|---|---|
Available |
vettore di | Available[j] = k: istanze di libere |
Max |
matrice | Max[i][j] = k: può chiedere al più istanze di |
Allocation |
Allocation[i][j] = k: possiede ora istanze di |
|
Need |
Need[i][j] = Max[i][j] - Allocation[i][j]: quante ne potrebbe ancora chiedere |
Il confronto tra vettori è componente per componente: vuol dire per ogni .
Algoritmo di sicurezza (è sicuro lo stato?):
Work = Available; Finish[i] = false per ogni i
finché esiste i con Finish[i] == false e Need[i] <= Work:
Work = Work + Allocation[i]; Finish[i] = true
se Finish[i] == true per ogni i -> stato SICURO altrimenti -> NON sicuroWork è la quantità di risorse che si renderebbe disponibile se i processi già "finiti" restituissero quanto possiedono. La sequenza in cui i processi vengono marcati è la sequenza sicura (non è unica).
Algoritmo di richiesta. Sia Request[i] il vettore con cui chiede risorse:
- se
Request[i] <= Need[i]si va al passo 2, altrimenti errore (il processo supera il massimo dichiarato); - se
Request[i] <= Availablesi va al passo 3, altrimenti attende (risorse non disponibili); - si simula l'assegnazione:
Available -= Request[i],Allocation[i] += Request[i],Need[i] -= Request[i]; si esegue l'algoritmo di sicurezza: se lo stato è sicuro si assegna davvero, se non sicuro attende e lo stato precedente viene ripristinato.
Complessità. L'algoritmo di sicurezza ha un ciclo esterno al più volte; in ogni passata si scorrono gli processi e per ciascuno si confrontano componenti: (v. Esercizio - Implementare il banchiere e calcolarne la complessità (eserciziario, esercizio 3 e laboratorio 6)).
Esempio svolto
Cinque thread , tre tipi di risorse: (10 istanze), (5), (7). Fotografia all'istante :
| Allocation | Max | Need Max Alloc | |
|---|---|---|---|
| 0 1 0 | 7 5 3 | 7 4 3 | |
| 2 0 0 | 3 2 2 | 1 2 2 | |
| 3 0 2 | 9 0 2 | 6 0 0 | |
| 2 1 1 | 2 2 2 | 0 1 1 | |
| 0 0 2 | 4 3 3 | 4 3 1 |
Totale assegnato: ; Available .
Il sistema è sicuro? Si prova la sequenza :
| passo | processo scelto | ? | dopo () |
|---|---|---|---|
| inizio | |||
| 1 | sì | ||
| 2 | sì | ||
| 3 | sì | ||
| 4 | sì | ||
| 5 | sì |
Tutti finiscono: stato sicuro. (Alla fine Work è uguale al totale delle risorse, come deve essere.)
chiede . Controllo: sì; sì. Nuovo stato: Available , , . La sequenza è sicura (: , ; : ; : ; : ; : ): la richiesta si accetta.
Poi chiede : (la non basta): attende.
Poi chiede : è e . Dopo l'assegnazione Available e i bisogni sono : , : , : , : , : . Nessuno ha ( vuole 2 di ma ce n'è 1; vuole 1 di e ce ne sono 0; gli altri vogliono più di 2 di ): stato non sicuro, la richiesta non si accetta anche se le risorse ci sono.
Rilevamento
L'altra strategia: lasciar entrare il sistema in deadlock, rilevarlo con un algoritmo e ripristinarlo.
Una sola istanza per risorsa: grafo wait-for
Si mantiene un grafo wait-for (di attesa): i nodi sono solo i processi e c'è un arco se aspetta una risorsa posseduta da . Si ottiene dal grafo di allocazione togliendo i nodi risorsa e collassando gli archi in . Periodicamente si cerca un ciclo: se c'è, c'è deadlock. La ricerca di un ciclo costa nel numero di archi, come in Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →.
Più istanze per risorsa
Si usa un algoritmo analogo a quello del banchiere, con Allocation e una matrice Request (le richieste correnti di ogni processo, invece di Need). Si parte da Work = Available; i processi senza risorse assegnate (Allocation[i] = 0) sono considerati finiti; poi si cerca un non finito con Request[i] <= Work: lo si "completa" e si somma Allocation[i] a Work. Alla fine, i processi non finiti sono in deadlock.
Esempio: cinque thread, tipi (7 istanze), (2), (6); Available .
| Allocation | Request | |
|---|---|---|
| 0 1 0 | 0 0 0 | |
| 2 0 0 | 2 0 2 | |
| 3 0 3 | 0 0 0 | |
| 2 1 1 | 1 0 0 | |
| 0 0 2 | 0 0 2 |
Svolgimento: può finire (): ; pure: ; ora (): ; (): ; (): . La sequenza completa tutti: nessun deadlock.
Se ora chiede un'altra istanza di (): si recuperano le risorse di , ma non basta per nessun altro ( chiede 1 di , ce ne sono 0; chiede e ; chiede ; chiede ): i processi , , , sono in deadlock.
Quando eseguire il rilevamento
Dipende da quanto spesso è probabile un deadlock e da quanti processi vanno annullati (uno per ogni ciclo disgiunto). Se l'algoritmo è lanciato a caso ci possono essere molti cicli nel grafo e non si saprebbe quale dei processi bloccati ha "causato" lo stallo.
Ripristino
Terminare i processi: si abortiscono tutti i processi in deadlock, oppure uno alla volta finché il ciclo sparisce. Si sceglie secondo: priorità, quanto ha già calcolato e quanto manca, risorse usate e risorse che ancora servono, quanti processi vanno terminati, se è interattivo o batch.
Prelazione delle risorse: (1) scegliere una vittima minimizzando un costo; (2) rollback: riportare il processo a uno stato sicuro e farlo ripartire da lì; (3) evitare la starvation: la stessa vittima potrebbe essere scelta sempre, quindi si include il numero di rollback nel costo.
Esercizi collegati
- Esercizio - Algoritmo del banchiere su tre risorse con richieste (eserciziario, esercizio 4)
- Esercizio - Algoritmo del banchiere su quattro risorse (eserciziario, esercizio 5)
- Esercizio - Assegnare risorse a un processo e mantenere lo stato sicuro (eserciziario, esercizio 117)
- Esercizio - Grafo di allocazione delle risorse e individuazione dei cicli (eserciziario, esercizi 20, 21, 56, 106 e 109)
- Esercizio - Deadlock tra due processi con lock incrociati (eserciziario, esercizi 92 e 116)
- Esercizio - Implementare il banchiere e calcolarne la complessità (eserciziario, esercizio 3 e laboratorio 6)
- Esercizio - Deadlock con semafori e thread (laboratorio 6)
Domande d'esame
1. Elencare le quattro condizioni del deadlock e dire quale si nega ordinando le risorse. Perché funziona? Traccia. Mutua esclusione, hold and wait, no preemption, attesa circolare. L'ordinamento totale nega l'attesa circolare: se ogni processo chiede le risorse in ordine crescente di indice, un ciclo richiederebbe che qualcuno chieda una risorsa di indice minore di una che già possiede, contro la regola.
2. Cos'è uno stato sicuro? Uno stato non sicuro è sempre un deadlock? Traccia. Esiste una sequenza di tutti i processi in cui ognuno può finire con le risorse disponibili più quelle liberate dai precedenti. Se è sicuro non ci sarà deadlock. Non sicuro non significa deadlock, solo che esiste la possibilità: alcuni processi potrebbero non chiedere il loro massimo. L'evitamento (banchiere) lo impedisce comunque.
3. Dato un grafo di allocazione con un ciclo, si può concludere che c'è deadlock? Traccia. Solo se ogni risorsa del ciclo ha una sola istanza. Con più istanze il ciclo è necessario ma non sufficiente: un'istanza posseduta da un processo fuori dal ciclo può liberarsi e sciogliere l'attesa; si verifica con l'algoritmo del banchiere o di rilevamento.
Versione ripasso
- Deadlock: insieme di processi in attesa di eventi (rilasci di risorse) causabili solo da processi dell'insieme. Uso di una risorsa: richiesta, uso, rilascio.
- Quattro condizioni insieme: mutua esclusione, hold and wait, no preemption, attesa circolare.
- Grafo di allocazione: processi (cerchi), risorse (rettangoli con un punto per istanza), arco di richiesta , di assegnazione . Senza cicli: niente deadlock; ciclo + istanza singola: deadlock; ciclo + più istanze: possibile. Rimandi: Grafi diretti e ordinamento topologicoGrafi diretti (archi orientati, grado entrante e uscente, cammini e cicli diretti), visite su grafi diretti e tipi di archi; DAG; ordinamento topologico con l'algoritmo basato sui gradi entranti e con la DFS (reverse postorder), correttezza e costo Theta(n+m); esempio svolto.Grafi diretti e ordinamento topologico →.
- Strategie: prevenire, evitare, rilevare e ripristinare, ignorare (Linux, Windows).
- Prevenzione: nega mutua esclusione (impossibile per risorse non condivisibili), hold and wait (tutte insieme: bassa utilizzazione), no preemption (rilascia tutto, riparte), attesa circolare: ordine totale delle risorse e richieste crescenti.
- Stato sicuro: esiste una sequenza sicura di tutti i processi; sicuro no deadlock, non sicuro deadlock possibile. Grafo con archi di rivendicazione (istanza singola): si concede solo se non si forma un ciclo.
- Banchiere:
Available,Max,Allocation,Need = Max - Allocation. Sicurezza:Work=Available; cerca conFinish[i]=falseeNeed[i]<=Work,Work+=Allocation[i]; sicuro se finiscono tutti. Richiesta:Request<=Need(altrimenti errore),Request<=Available(altrimenti attende), simula e verifica la sicurezza. Complessità . - Esempio: , Available : sicuro con ; concessa; poi attende, rifiutata (non sicuro).
- Rilevamento: istanza singola = grafo wait-for (ciclo = deadlock); più istanze = come il banchiere con
Requestal posto diNeed(processi senza risorse sono finiti); i non finiti sono in deadlock. Esempio : nessun deadlock; con che chiede un altro : deadlock di . - Ripristino: terminare tutti o uno alla volta (criteri di scelta); prelazione con vittima, rollback, attenzione alla starvation (contare i rollback nel costo).
- Esempio di grafo senza stallo: (1), (2), (1); ha e attende ; ha , e attende ; ha : nessun ciclo. Se chiede anche : ciclo e le due istanze di sono di e deadlock.
- Prevenzione, svantaggi: hold and wait (tutte all'inizio) bassa utilizzazione e starvation; no preemption solo per risorse salvabili; circular wait ordine totale e richieste crescenti (l'ultimo dovrebbe chiedere un indice minore di quello che possiede).
- Stato sicuro: sequenza di tutti i processi con . Sicuro no deadlock; non sicuro deadlock possibile.
- Esempio del banchiere (, Available ): Need , , , , ; sequenza con Work , , , , . concessa; poi attende (Available ); rifiutata (dopo: Available , nessun Need ).
- Rilevamento, esempio (): Allocation , , , , ; Request , , , , ; Available ; sequenza nessun deadlock. Se chiede un altro : Work non basta, deadlock di .
- Grafo wait-for (istanza singola): nodi solo i processi, se attende una risorsa di ; un ciclo = deadlock; ricerca del ciclo . Quando eseguire il rilevamento: dipende da quanto spesso capita e da quanti processi vanno annullati (uno per ciclo disgiunto).
- Ripristino: terminare tutti i processi coinvolti o uno alla volta (criteri: priorità, tempo già calcolato e residuo, risorse usate e necessarie, quanti da terminare, interattivo o batch); prelazione: scegliere la vittima (costo minimo), rollback a uno stato sicuro, evitare la starvation contando i rollback nel costo.
- Complessità del banchiere: al più giri processi componenti = ; si paga a ogni richiesta.
- Errori tipici: confondere stato non sicuro e deadlock; dimenticare
Request <= Need(errore) prima diAvailable; confrontare vettori sul totale invece che componente per componente; dire che un ciclo implica sempre deadlock; scambiareNeedconAllocation.
Esercizi su questo argomento
- Esercizio - Algoritmo del banchiere su quattro risorse (eserciziario, esercizio 5)
- Esercizio - Algoritmo del banchiere su tre risorse con richieste (eserciziario, esercizio 4)
- Esercizio - Assegnare risorse a un processo e mantenere lo stato sicuro (eserciziario, esercizio 117)
- Esercizio - Deadlock con semafori e thread (laboratorio 6)
- Esercizio - Deadlock tra due processi con lock incrociati (eserciziario, esercizi 92 e 116)
- Esercizio - Grafo di allocazione delle risorse e individuazione dei cicli (eserciziario, esercizi 20, 21, 56, 106 e 109)
- Esercizio - Implementare il banchiere e calcolarne la complessità (eserciziario, esercizio 3 e laboratorio 6)
- Esercizio - Spogliatoi e cesti della piscina (eserciziario, esercizio 9)