11.04.2013 Visualizzazioni

Sezione critica

Sezione critica

Sezione critica

SHOW MORE
SHOW LESS

Trasformi i suoi PDF in rivista online e aumenti il suo fatturato!

Ottimizzi le sue riviste online per SEO, utilizza backlink potenti e contenuti multimediali per aumentare la sua visibilità e il suo fatturato.

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!