11.04.2013 Views

Sezione critica

Sezione critica

Sezione critica

SHOW MORE
SHOW LESS

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!