Sezione critica
Sezione critica
Sezione critica
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
Sincronizzazione<br />
• Nell'informatica moderna, il concetto di<br />
sincronizzazione si riferisce a due importanti<br />
aspetti<br />
• Sincronizzazione dei processi<br />
– Insieme di meccanismi per coordinare<br />
l'esecuzione di diversi processi/thread<br />
– Insieme di meccanismi per garantire accesso<br />
esclusivo ai dati<br />
• Sincronizzazione dei dati<br />
– Insieme di meccanismi per mantenere coerenti<br />
molteplici copie distinte di dati<br />
1
Sincronizzazione processi e dati<br />
• Sincronizzazione dei processi<br />
– Lock<br />
– Semafori, mutex<br />
– Thread join<br />
– Segnali<br />
• Sincronizzazione dei dati<br />
– Sincronizzazione dispositivocomputer<br />
– Clustered file system<br />
– Coerenza della cache<br />
– RAID<br />
– Replicazione DB<br />
– Journaling<br />
2
Accesso esclusivo a dati condivisi<br />
• Nel corso di SO, ci concentriamo sui<br />
meccanismi di<br />
– sincronizzazione dei processi<br />
– accesso esclusivo ai dati condivisi<br />
• Il focus principale della lezione è sugli accessi<br />
esclusivi ai dati condivisi<br />
• Scenario:<br />
– n processi (o thread) P 0 ,P 1 ,...,P n-1 sequenziali<br />
cooperanti fra loro<br />
– tutti i processi in esecuzione asincrona e<br />
concorrente<br />
– condivisione di dati (semplici, strutture)<br />
3
Esempio: il problema bounded buffer<br />
• In tal senso, un classico scenario è costituito<br />
dal problema del buffer limitato (bounded<br />
buffer)<br />
– vettore di dimensione limitata, contenente m<br />
buffer<br />
– vettore condiviso tra n=2 processi<br />
– un processo “produttore” produce dati e li<br />
inserisce nel vettore<br />
– un processo “consumatore” preleva i valori dal<br />
vettore e li elabora<br />
• Il problema bounded buffer modella molto<br />
bene i problemi legati alla gestione<br />
(inserimento, prelievo) di una lista/coda<br />
4
Bounded buffer: produttore<br />
• L'algoritmo del produttore può essere<br />
sintetizzato come segue<br />
while (1) {<br />
while (contatore == DIM_VETTORE) {};<br />
vettore[ind_prod] = prodotto;<br />
ind_prod = (ind_prod + 1) % DIM_VETTORE;<br />
contatore++;<br />
}<br />
5
Bounded buffer: consumatore<br />
• L'algoritmo del consumatore può essere<br />
sintetizzato come segue<br />
while (1) {<br />
while (contatore == 0) {};<br />
consuma = vettore[ind_cons];<br />
ind_cons = (ind_cons + 1) % DIM_VETTORE;<br />
contatore--;<br />
}<br />
6
Bounded buffer: concorrenza<br />
• Se eseguite separatamente, le procedure ora<br />
descritte funzionano correttamente<br />
• Se eseguite concorrentemente, le procedure<br />
ora descritte possono non funzionare<br />
correttamente<br />
• ATTENZIONE:<br />
– L'array vettore[] è condiviso tra i due processi<br />
produttore e consumatore<br />
– L'intero contatore è condiviso tra i due processi<br />
produttore e consumatore<br />
7
Bounded buffer: concorrenza<br />
contatore++:<br />
registro 1 = contatore;<br />
registro 1 = registro 1 + 1;<br />
contatore = registro 1 ;<br />
contatore--:<br />
registro 2 = contatore;<br />
registro 2 = registro 2 - 1;<br />
contatore = registro 2 ;<br />
8
Bounded buffer: concorrenza<br />
• registro 1 e registro 2 possono essere lo stesso<br />
registro<br />
– context switch ripristina il valore corretto nel<br />
registro<br />
• Esecuzione concorrente delle istruzioni<br />
contatore++ e contatore--:<br />
– esecuzione sequenziale delle corrispondenti<br />
operazioni in linguaggio macchina<br />
– esecuzione interleaved<br />
♦ordine qualunque, purché preservi l'ordine<br />
interno delle istruzioni negli algoritmi<br />
9
Bounded buffer: concorrenza<br />
T 0 : prod. registro 1 =contatore {registro 1 =5}<br />
T 1 : prod. registro 1 =registro 1 +1 {registro 1 =6}<br />
T 2 : prod. contatore=registro 1 {contatore=6}<br />
T 3 : cons. registro 2 =contatore {registro 2 =6}<br />
T 4 : cons. registro 2 =registro 2 -1 {registro 2 =5}<br />
T 5 : cons. contatore=registro 2 {contatore=5}<br />
• Questa sequenza di istruzioni macchina<br />
produce il risultato corretto<br />
10
Bounded buffer: concorrenza<br />
T 0 : prod. registro 1 =contatore {registro 1 =5}<br />
T 1 : prod. registro 1 =registro 1 +1 {registro 1 =6}<br />
T 2 : cons. registro 2 =contatore {registro 2 =5}<br />
T 3 : cons. registro 2 =registro 2 -1 {registro 2 =4}<br />
T 4 : prod. contatore=registro 1 {contatore=6}<br />
T 5 : cons. contatore=registro 2 {contatore=4}<br />
• Questa sequenza di istruzioni macchina non<br />
produce il risultato corretto<br />
• Il risultato dell'operazione dipende dall'ordine<br />
di esecuzione delle istruzioni macchina<br />
imposto dallo scheduler<br />
11
Corsa <strong>critica</strong><br />
• Corsa <strong>critica</strong> (race condition, race): si ha una<br />
corsa <strong>critica</strong> quando l'esecuzione<br />
interallacciata di istruzioni di due o più<br />
processi (condividenti dati) dipende dall'ordine<br />
delle singole istruzioni scelte<br />
• Nell'esempio visto, per non avere corse<br />
critiche, è necessario che il codice macchina<br />
relativo a contatore++ o contatore-- sia<br />
eseguito atomicamente<br />
• Sincronizzazione tra processi<br />
12
<strong>Sezione</strong> <strong>critica</strong><br />
• Siano dati n processi P 0 ,P 1 ,...,P n-1<br />
– eseguiti concorrentemente<br />
– cooperanti tramite dati condivisi<br />
• Ciascun P j ha una porzione di codice che<br />
accede ai dati condivisi<br />
• <strong>Sezione</strong> <strong>critica</strong>: sezione di codice che accede a<br />
dati condivisi<br />
– va eseguita da al più un processo alla volta<br />
(mutua esclusione)<br />
13
<strong>Sezione</strong> <strong>critica</strong><br />
• Protocollo di sincronizzazione fra processi per<br />
l'accesso alla sezione <strong>critica</strong><br />
– sezione di ingresso: il processo chiede il<br />
permesso di entrare nella sezione <strong>critica</strong><br />
– sezione <strong>critica</strong>: accesso ai dati condivisi<br />
– sezione di uscita: rilascio dell'uso della sezione<br />
<strong>critica</strong><br />
– sezione non <strong>critica</strong>: codice la cui esecuzione<br />
non richiede un meccanismo di mutua<br />
esclusione<br />
• Attenzione: ciò che si protegge è il codice che<br />
accede ai dati condivisi<br />
• Non si protegge del codice che non fa accesso<br />
a dati condivisi<br />
14
Protezione delle sezioni critiche<br />
• Il meccanismo generale di protezione delle<br />
sezioni critiche si presenta nel formato<br />
seguente:<br />
do {<br />
sezione di ingresso;<br />
sezione <strong>critica</strong>;<br />
sezione di uscita;<br />
sezione non <strong>critica</strong>;<br />
} while (1);<br />
15
<strong>Sezione</strong> <strong>critica</strong><br />
• Ogni soluzione al problema della sezione<br />
<strong>critica</strong> deve soddisfare tre requisiti:<br />
1.mutua esclusione: se P j è in esecuzione, P i non può<br />
essere eseguito (i != j)<br />
2.progresso: se<br />
● nessun processo esegue la sezione <strong>critica</strong>,<br />
● alcuni processi vogliono eseguire la sezione<br />
<strong>critica</strong>,<br />
allora solo i processi che non eseguono le<br />
sezioni non critiche possono decidere<br />
l'ammissione in sezione <strong>critica</strong><br />
3.attesa limitata: se un processo è in attesa di<br />
ingresso nella sezione <strong>critica</strong>, altri processi<br />
possono eseguire la sezione <strong>critica</strong> al più k volte (k<br />
finito)<br />
16
Meccanismi attivi e passivi<br />
• Le tecniche di accesso in mutua esclusione<br />
possono essere suddivise in due categorie:<br />
tecniche attive e tecniche passive<br />
• Tecniche attive<br />
– La CPU monitora costantemente il verificarsi<br />
dell'evento “la risorsa si libera”<br />
– Il processo non si blocca<br />
• Tecniche passive<br />
– La CPU blocca il processo che intende<br />
acquisire una risorsa già prenotata<br />
– Il processo viene svegliato quando la risorsa si<br />
libera (l'evento si verifica)<br />
17
La soluzione di Peterson<br />
• Tecnica interamente software per garantire<br />
accesso in mutua esclusione ad una sezione<br />
<strong>critica</strong><br />
• Sfortunatamente, tale soluzione può non<br />
funzionare sulle CPU moderne che, per motivi<br />
di efficienza, fanno reordering delle istruzioni<br />
• Essa rappresenta, tuttavia, un buon esempio di<br />
come affrontare le difficoltà legate al<br />
soddisfacimento dei requisiti per l'accesso in<br />
mutua esclusione<br />
18
La soluzione di Peterson<br />
• Due processi P 0 e P 1 si alternano nella loro<br />
esecuzione<br />
• Per comodità di rappresentazione, l'algoritmo<br />
sarà descritto tramite due indici:<br />
– Indice i: indice del processo che sta eseguendo<br />
codice sulla CPU locale<br />
– Indice j: indice dell'altro processo (j=1-i)<br />
• I due processi condividono due informazioni<br />
– Una variabile intera turn, contenente l'indice del<br />
processo abilitato ad entrare in sezione <strong>critica</strong><br />
– Un array di boolean flag[2] che, per ciascun<br />
processo, mi dice se tale processo è pronto per<br />
entrare in sezione <strong>critica</strong><br />
19
La soluzione di Peterson<br />
• La soluzione di Peterson è rappresentata<br />
dall'algoritmo seguente (processo P i ):<br />
do {<br />
flag[i] = TRUE;<br />
turn = j;<br />
while (flag[j] && turn==j);<br />
sezione <strong>critica</strong>;<br />
flag[i] = FALSE;<br />
} while (1);<br />
<strong>Sezione</strong> di ingresso<br />
<strong>Sezione</strong> di uscita<br />
20
La soluzione di Peterson<br />
• Sono verificati i requisiti per il corretto<br />
accesso in mutua esclusione?<br />
• Requisito 1: mutua esclusione<br />
– P i entra nella sua sezione <strong>critica</strong> solo se fallisce<br />
il while, ossia flag[j]==false o turn==i<br />
– se P 0 e P 1 fossero in sezione <strong>critica</strong> nello stesso<br />
istante, si avrebbe flag[0]==TRUE,<br />
flag[1]==TRUE<br />
– le due oss. precedenti implicano che turn=0 e<br />
turn=1 contemporaneamente<br />
– assurdo -> P 0 e P 1 non possono mai eseguire la<br />
sezione <strong>critica</strong> concorrentemente<br />
21
La soluzione di Peterson<br />
• Sono verificati i requisiti per il corretto<br />
accesso in mutua esclusione?<br />
• Requisito 2: progresso<br />
– P i non entra in sezione <strong>critica</strong> fino a quando<br />
viene eseguito il ciclo while<br />
(flag[j]==TRUE e turn==j)<br />
– se P j non è pronto ad entrare in sezione <strong>critica</strong>,<br />
imposta flag[j]==false, ed entra P i<br />
– se invece P j entra in sezione <strong>critica</strong>, quando<br />
esce imposta flag[j]=false e P i entra in sezione<br />
<strong>critica</strong><br />
– progresso (P i e P j si alternano)<br />
22
La soluzione di Peterson<br />
• Sono verificati i requisiti per il corretto<br />
accesso in mutua esclusione?<br />
• Requisito 3: attesa limitata<br />
– La dimostrazione precedente (requisito 2) ha<br />
mostrato come P i e P j si alternino<br />
nell'esecuzione della sezione <strong>critica</strong><br />
– Se un processo è in attesa di entrare in sezione<br />
<strong>critica</strong>, l'altro processo può eseguire la sezione<br />
<strong>critica</strong> al più k=1 volte<br />
23
Strumenti moderni per la<br />
sincronizzazione<br />
• La soluzione di Peterson, pur essendo basata<br />
completamente su software<br />
– è complessa<br />
– mal sopporta ottimizzazioni sul codice da parte<br />
della CPU e del SO<br />
• Nei moderni SO, le sezioni di ingresso e di<br />
uscita sono implementate tramite un modello<br />
universale: il lock (serratura, lucchetto)<br />
• Si protegge una sezione <strong>critica</strong> che accede a<br />
dati condivisi con<br />
– una variabile intera che indica se la risorsa è<br />
libera oppure occupata<br />
– un meccanismo che controlla lo stato della<br />
variabile intera e permette l'accesso esclusivo<br />
24
Modello di locking/unlocking<br />
• Il meccanismo generale di protezione delle<br />
sezioni critiche tramite lock si presenta nel<br />
formato seguente:<br />
do {<br />
<br />
sezione <strong>critica</strong>;<br />
<br />
sezione non <strong>critica</strong>;<br />
} while (1);<br />
25
Supporto hardware al locking<br />
• Molte architetture offrono particolari istruzioni<br />
che permettono di leggere/scrivere il<br />
contenuto della memoria in modalità atomica<br />
– Esecuzione atomica: esecuzione non<br />
interrompibile (tutto o niente)<br />
– La tipica operazione atomica è un controllo di<br />
una variabile con contestuale modifica<br />
• Le operazioni di locking sono spesso<br />
implementate tramite tali istruzioni macchina<br />
• Le istruzioni macchine permettono di<br />
implementare meccanismi di attesa attiva<br />
26
Supporto hardware al locking<br />
• Serve davvero un meccanismo di locking di<br />
questo tipo?<br />
• Sistemi single processor<br />
– Non è necessario un meccanismo di locking<br />
atomico<br />
– Basta disabilitare le interruzioni durante la<br />
esecuzione della sezione <strong>critica</strong><br />
• Sistemi multiple processor:<br />
– Non basta disabilitare le interruzioni sulla CPU<br />
locale, perché l'accesso concorrente potrebbe<br />
avvenire da una altra CPU<br />
– Bisognerebbe spegnere le interruzioni su tutte<br />
le CPU; tuttavia, il sistema non scalerebbe più<br />
– le sezioni critiche devono essere protette<br />
27
Istruzione TestAndSet<br />
• L'istruzione macchina TestAndSet effettua le<br />
seguenti operazioni atomicamente:<br />
– ritorna il vecchio valore di una variabile<br />
– imposta la variabile al valore TRUE (1)<br />
boolean TestAndSet (boolean *obiettivo) {<br />
}<br />
boolean valore = *obiettivo;<br />
*obiettivo = TRUE;<br />
return valore;<br />
28
Locking/Unlocking con TestAndSet<br />
• Si usa una variabile intera blocco per<br />
contenere l'informazione “risorsa libera o<br />
occupata”<br />
– blocco=FALSE: la risorsa è libera<br />
– blocco=TRUE: la risorsa è occupata<br />
do {<br />
while (TestAndSet(blocco));<br />
sezione <strong>critica</strong>;<br />
blocco = FALSE;<br />
} while (1);<br />
29
Locking/Unlocking con TestAndSet<br />
blocco<br />
1<br />
0 t *<br />
blocco=FALSE<br />
t<br />
30
Istruzione Swap<br />
• L'istruzione Swap effettua le seguenti<br />
operazioni atomicamente:<br />
– scambia i valori di due variabili a e b<br />
void Swap(boolean *a, boolean *b) {<br />
}<br />
boolean temp = *a;<br />
*a = *b;<br />
*b = temp;<br />
31
Locking/Unlocking con Swap<br />
• Si usano due variabile intere:<br />
– blocco: FALSE se la risorsa è libera, TRUE se la<br />
risorsa è occupata<br />
– chiave: variabile di aiuto per lo swap<br />
do {<br />
chiave = TRUE;<br />
while (chiave==TRUE) {<br />
Swap(blocco, chiave);<br />
}<br />
sezione <strong>critica</strong>;<br />
blocco = FALSE;<br />
} while (1);<br />
32
locco<br />
Locking/Unlocking con Swap<br />
1<br />
0 t *<br />
blocco=FALSE<br />
t<br />
33
Istruzioni hw e mutua esclusione<br />
• TestAndSet e Swap non soddisfano il criterio<br />
di attesa limitata<br />
• TestAndSet e Swap vanno integrate con un<br />
supporto (algoritmo) per permettere l'attesa<br />
limitata<br />
34
Semafori<br />
• Le soluzioni al problema della sezione <strong>critica</strong><br />
viste in precedenza sono vincolate al<br />
particolare tipo di problema<br />
• Esiste un meccanismo più generale?<br />
– Meccanismo semplice per decidere se utilizzare<br />
o meno una risorsa condivisa tramite una<br />
sezione <strong>critica</strong><br />
– Mascherare le particolarità della applicazione<br />
(niente riferimenti a struttura dati interne tipo<br />
flag[])<br />
• Il meccanismo esiste e prende il nome di<br />
semaforo<br />
– Il nome non è casuale: emula un vero e proprio<br />
semaforo stradale<br />
35
Semafori<br />
• Semaforo: variabile intera S, che conta il<br />
numero di slot di uso della risorsa condivisa<br />
• La variabile S viene acceduta solo tramite due<br />
funzioni:<br />
– wait(S): sezione di accesso (originariamente<br />
chiamata P(), dall'olandese proberen, ossia<br />
“testare”)<br />
– signal(S): sezione di uscita (originariamente<br />
chiamata V(), dall'olandese verhogen, ossia<br />
“incrementare”)<br />
36
wait() e signal()<br />
• Lo scheletro implementativo delle funzioni<br />
wait() e signal() è il seguente<br />
wait(S) {<br />
while( S
Semafori contatore e binari<br />
• Dal punto di vista degli slot di risorsa messi a<br />
disposizione, si possono distinguere due<br />
categorie di semafori: semafori contatore e<br />
semafori binari<br />
• Semafori contatore:<br />
– S è inizializzata ad un valore > 1<br />
– Una risorsa che può elaborare S > 1 elementi<br />
concorrentemente<br />
• Semafori binari:<br />
– S è inizializzata ad 1 (caso comune)<br />
– O uso la risorsa, o non la uso<br />
– Viene anche detto mutex (MUTual EXclusion)<br />
38
Semafori attivi e passivi<br />
• Il modello di semaforo ora presentato è attivo<br />
(busy waiting)<br />
– Dal punto di vista funzionale, somiglia in<br />
maniera sinistra ai meccanismi di locking hw<br />
• Un semaforo attivo prende il nome di spinlock<br />
– La CPU continua a ciclare (spin) sul while,<br />
bloccando (lock) l'accesso<br />
• Esiste un modello passivo di semaforo?<br />
39
Semafori attivi e passivi<br />
• In generale, un semaforo può essere<br />
implementato in due modalità distinte:<br />
– busy waiting (spinlock): la CPU controlla<br />
attivamente il verificarsi della condizione di<br />
accesso alla sezione <strong>critica</strong><br />
♦scalabile, veloce<br />
♦CPU-intensive<br />
♦adatto per attese brevi (accesso a memoria)<br />
– sleep (mutex,semaforo): il processo viene<br />
messo in attesa (sleep) che si verifichi la<br />
condizione di accesso alla sezione <strong>critica</strong><br />
♦più lento<br />
♦adatto per attese lunghe (I/O)<br />
40
Struttura semaforo passivo<br />
• Nel caso passivo, il semaforo diventa una<br />
struttura dati contenente:<br />
– la singola variabile intera S, che conta il numero<br />
di slot associati alla risorsa<br />
– una lista di processi che attendono lo sblocco<br />
della risorsa<br />
typedef struct {<br />
int valore;<br />
struct processo *p;<br />
} semaforo;<br />
puntatore alla lista dei<br />
processi in attesa di<br />
entrare nella sezione<br />
<strong>critica</strong><br />
41
Semaforo passivo: wait()<br />
• La funzione wait(S) si estende con il supporto<br />
di accodamento dei processi<br />
void wait(semaforo S) {<br />
S.valore--;<br />
if (S.valore < 0) {<br />
;<br />
}<br />
}<br />
block();<br />
mette il processo in stato<br />
di attesa<br />
42
Semaforo passivo: signal()<br />
• La funzione signal(S) si estende con il<br />
supporto di prelievo di un processo dalla coda<br />
void signal(semaforo S) {<br />
S.valore++;<br />
if (S.valore
Dettagli implementativi<br />
• Osservazioni<br />
– le funzioni block() e wakeup() devono essere<br />
fornite dal SO<br />
– il criterio di estrazione usato nella wakeup() è,<br />
generalmente, FIFO, ma può non essere il solo<br />
– l'esecuzione di wait() e signal() è atomica<br />
♦wait() e signal() considerate come vere e<br />
proprie sezioni critiche<br />
– nelle versioni bloccanti, l'attesa attiva non è<br />
completamente eliminata; è limitata alla attesa<br />
attiva della sezione <strong>critica</strong> wait()/signal()<br />
(abbastanza breve)<br />
44
Problemi: stallo<br />
• Nell'implementazione bloccante dei semafori,<br />
può capitare, a seguito di un errore<br />
implementativo, che:<br />
– un insieme di processi attenda il verificarsi<br />
di un evento<br />
– l'evento in questione può essere causato<br />
solo da uno dei processi in attesa<br />
• Tale situazione prende il nome di stallo<br />
(deadlock)<br />
45
P 0<br />
wait(S);<br />
wait(Q);<br />
...<br />
signal(S);<br />
signal(Q);<br />
Problemi: stallo<br />
P 1<br />
wait(Q);<br />
wait(S);<br />
...<br />
signal(Q);<br />
signal(S);<br />
Errore tipico:<br />
non viene preservato l'ordine<br />
di richiesta/rilascio delle sezioni critiche<br />
46
Problemi: attesa indefinita<br />
• In generale, si evita il criterio LIFO (stack) nella<br />
gestione della coda dei processi in attesa della<br />
sezione <strong>critica</strong><br />
• Il criterio LIFO favorisce gli ultimi processi e<br />
non i primi<br />
• Attesa indefinita dei processi più vecchi<br />
• Si adotta il criterio FIFO (coda)<br />
– Le wait queue del kernel<br />
47
Problemi tipici di sincronizzazione<br />
• Problema produttore-consumatore con<br />
memoria limitata<br />
– vettore di n posizioni<br />
– ciascuna posizione gestisce un elemento<br />
– un processo produttore di elementi<br />
– un processo consumatore di elementi<br />
– si implementa con tre semafori:<br />
♦vuote: conta le posizioni vuote del vettore<br />
(inizializzato ad n)<br />
♦piene: conta le posizioni piene del vettore<br />
(inizializzato a 0)<br />
♦mutex: gestisce la mutua esclusione al<br />
vettore (inizializzato a 1)<br />
48
Problemi tipici di sincronizzazione<br />
• Problema produttore-consumatore con<br />
memoria limitata<br />
Processo produttore<br />
do {<br />
appena_prodotto = ;<br />
wait(vuote);<br />
wait(mutex);<br />
;<br />
signal(mutex);<br />
signal(piene);<br />
} while (1);<br />
49
Problemi tipici di sincronizzazione<br />
• Problema produttore-consumatore con<br />
memoria limitata<br />
Processo consumatore<br />
do {<br />
wait(piene);<br />
wait(mutex);<br />
;<br />
da_consumare=;<br />
signal(mutex);<br />
signal(vuote);<br />
} while (1);<br />
50
Problemi tipici di sincronizzazione<br />
• Problema dei lettori e degli scrittori<br />
– n processi concorrenti agenti su un insieme di<br />
dati (ad esempio, un file)<br />
– alcuni processi sono lettori<br />
– gli altri processi sono scrittori<br />
– più lettori possono accedere concorrentemente<br />
all'insieme dei dati<br />
– ciascuno scrittore ha accesso esclusivo<br />
all'insieme dei dati<br />
51
Problemi tipici di sincronizzazione<br />
• Problema dei lettori e degli scrittori<br />
– num_lettori: intero condiviso che indica il<br />
numero di processi attualmente in lettura<br />
(inizializzato a 0)<br />
– due semafori:<br />
♦mutex: garantisce l'accesso in mutua<br />
esclusione a num_lettori (inizializzato a 1)<br />
♦scrittura: garantisce l'accesso in scrittura<br />
mutuamente esclusiva alla risorsa<br />
(inizializzato a 1)<br />
52
Problemi tipici di sincronizzazione<br />
• Problema lettore-scrittore<br />
Processo scrittore<br />
wait(scrittura);<br />
;<br />
signal(scrittura);<br />
53
Problemi tipici di sincronizzazione<br />
• Problema lettore-scrittore<br />
Processo lettore<br />
wait(mutex);<br />
num_lettori++;<br />
if (num_lettori == 1)<br />
wait(scrittura);<br />
signal(mutex);<br />
<br />
wait(mutex);<br />
num_lettori--;<br />
if (num_lettori == 0)<br />
signal(scrittura);<br />
signal(mutex);<br />
aggiornamento di num_lettori in<br />
mutua esclusione tra i lettori<br />
se non ho lettori, prima di<br />
leggere, aspetta uno scrittore<br />
permetti ad un altro lettore di<br />
avanzare<br />
aggiornamento di num_lettori in<br />
mutua esclusione tra i lettori<br />
se non ho lettori, permetti ad<br />
un'altro scrittore di avanzare<br />
permetti ad un altro lettore di<br />
avanzare<br />
54
Problemi tipici di sincronizzazione<br />
• Problema dei cinque filosofi<br />
– 5 filosofi mangiano e pensano<br />
– sono seduti ad un tavolo rotondo<br />
– la tavola è apparecchiata con 5 bacchette, 5<br />
piatti ed 1 scodella di riso<br />
– quando 1 filosofo pensa, non interagisce con gli<br />
altri<br />
– quando 1 filosofo mangia:<br />
♦prova a prendere le bacchette, una alla volta<br />
♦se ci riesce, mangia a sazietà<br />
♦al termine del pasto, lascia le bacchette e si<br />
rimette a pensare<br />
55
Problemi tipici di sincronizzazione<br />
Cartesio<br />
Platone<br />
Voltaire Socrate<br />
Confucio<br />
56
Problemi tipici di sincronizzazione<br />
• Problema dei cinque filosofi<br />
– i filosofi non possono mangiare tutti<br />
contemporaneamente (mancano le bacchette)<br />
– voglio evitare lo stallo, in cui ciascun filosofo<br />
prende la bacchetta di sinistra<br />
– il problema dei cinque filosofi rappresenta una<br />
classe di problemi ben definita:<br />
♦assegnazione di risorse limitate a diversi<br />
processi, senza creare situazioni di stallo<br />
– Bacchette=Risorse<br />
– Provo ad associare a ciascuna bacchetta un<br />
semaforo<br />
57
Problemi tipici di sincronizzazione<br />
• Problema dei cinque filosofi<br />
do {<br />
wait(bacchetta[i]);<br />
wait(bacchetta[(i+1) % 5]);<br />
;<br />
signal(bacchetta[i]);<br />
signal(bacchetta[(i+1) % 5]);<br />
<br />
} while (1);<br />
prova a prendere le<br />
bacchette alla mia<br />
destra e sinistra<br />
rilascia le<br />
bacchette alla mia<br />
destra e sinistra<br />
58
Problemi tipici di sincronizzazione<br />
• Problema dei cinque filosofi<br />
– questa soluzione non è in grado di evitare lo<br />
stallo<br />
♦tutti i filosofi tentano di afferrare la bacchetta<br />
alla loro sinistra<br />
– bacchetta[i]=0 per ogni i<br />
– wait(bacchetta[(i+1)] % 5]) stalla<br />
♦i filosofi muoiono tutti di fame<br />
– starvation dei processi<br />
59
Problemi tipici di sincronizzazione<br />
• Problema dei cinque filosofi<br />
– Possibili soluzioni:<br />
♦far sedere al tavolo al più 4 filosofi (limitare il<br />
numero di processi richiedenti la risorsa)<br />
♦un filosofo può prendere le bacchette solo se<br />
entrambe disponibili (il processo o prende<br />
tutte le risorse di cui ha bisogno in mutua<br />
esclusione o non progredisce)<br />
♦un filosofo dispari prende prima la bacchetta<br />
di sinistra e poi quella di destra, un filosofo<br />
pari fa il contrario (asimmetria delle richieste<br />
spezza la catena di stallo)<br />
60