I threads verranno schedulati automaticamente per essere mandati in esecuzione all'interno del processo stesso. Questi thread possono avere delle zone di memoria condivise ( possono essere delle variabili globali contenute nel data segment oppure dei file comuni). Se i thread dovessero solo leggere il dato / file non ci sarebbe nessun problema, ma se dovessero anche modificarlo? Basti pensare al caso di un conto corrente:
Thread 1 | Thread 2 | Bilancio |
---|---|---|
Legge Bilancio: $1000 | $1000 | |
Legge Bilancio: $1000 | $1000 | |
Deposito $200 | $1000 | |
Deposito $200 | $1000 | |
Aggiorna Bilancio $1000+$200 | $1200 | |
Aggiorna Bilancio $1000+$200 | $1200 |
Il primo thread legge il bilancio attuale (1000€) così come il secondo che esegue un deposito di 200€.
Viene eseguito un deposito da parte del primo thread, che va ad aggiornare il bilancio.
Il secondo thread non avrà il bilancio aggiornato (1200€) ma avrà il bilancio letto in precedenza (1000€), quindi quando si andrà ad aggiornare il valore si avrà solo l'ultima modifica fatta dal secondo thread e non quella fatta dal primo.
Questo esempio è uno dei tanti e fa capire quanto sia importante la gestione di thread o processi quando devono andare a scrivere su dei dati condivisi.
Per risolvere questo problema utilizziamo le variabili mutex o le variabili semaforo.
Nella gestione di più processi o thread che eseguono delle corse critiche abbiamo 3 problemi ricorrenti, che sono i seguenti:
- Le cinque filosofe a tavola
- Il barbiere che dorme
- Lettore e scrittore
- Produttore e consumatore
Il primo problema è il seguente: 5 filosofe sono ad un tavolo rotondo per mangiare degli spaghetti. Ogni filosofa dispone di una sola forchetta ma sfortunatamente gli spaghetti sono così scivolosi che per mangiarli bisogna utilizzare per forza 2 forchette insieme.
Le nostre filosofe (cioè i nostri processi) possono essere in 3 stati distinti: pensante , affamata e mangiante .
Come possiamo far mangiare il maggior numero di filosofe contemporaneamente senza creare nessuna situazione di stallo?
Utilizzeremo un vettore di interi state di un numero uguale a quello delle filosofe; in ognuno di queste posizioni andremo ad inserire lo stato della nostra filosofa (pensante, affamata e mangiante).
In totale avremo 6 mutex: 5 per ogni filosofa mentre ne avremo una globale.
Ogni filosofa può iniziare a mangiare solo se nessuna delle 2 filosofe ai suoi fianchi non sta mangiando.
Il codice per risolvere questo problema è il seguente:
#define N 5
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef int semaphore;
int state[N];
semaphore Mutex=1; /*Master Mutex*/
semaphore S[N]; /*Philosopher's Mutex, Inizializzato come? tutto a zero, ovvero bloccato*/
/*down circa= lock. up circa= unlock. */
void philosopher(int i) //Codice eseguito dalle 5 filosofe
{
while (1)
{
think(); // La filosofa sta pensando
take_forks(i); // la filosofa è affamata, quindi cercherà di prendere le forchette
eat(); // entra nella sezione critica, inizia a mangiare
put_forks(i); // posa le forchette e ricomincia a pensare
}
}
void take_forks(int i) //Funzione prendi forchette
{
down(&Mutex); //Cerca di entrare nella zona critica
state[i]=HUNGRY; //Si modifica il suo stato rendendolo da pensante ad affamata
test(i); // controlla che le forchette siano disponibili, quindi che le due filosofe da parte non stiano mangiando
up(&Mutex); //Esce dalla regione critica
down(&S[i]); // Se la funzione test è andata a buon fine, quindi la filosofa ha ottenuto le forchette essa inizierà a mangiare, altrimenti si bloccherà aspettando che una delle filosofe ai suoi lati la sblocchi, così che possa iniziare a mangiare
}
void put_forks(int i) //Funzione posa forchette
{
down (&Mutex); //Cerca di entrare nella zona critica
state[i]=THINKING; //Ha finito di mangiare, quindi il suo stato viene modificato in pensante
test( Left(i) ) ; // Controlla che la filosofa alla sua sinistra possa mangiare
test( Right(i) ); // Controlla che la filosofa alla sua destra possa mangiare
up(&Mutex); //Esce dalla regione critica
}
void test(i) //Funzione che controlla che le filosofe possano iniziare a mangiare
{
if(state[i]== HUNGRY && state[ Left(i) ]!=EATING && state[ Right(i) ]!=EATING )
//Se la filosofa è affamata e nessuna delle filosofe vicine sta mangiando, allora la filosofa può mangiare
{
state[i]=EATING; // Si mette nello stato mangiante
up(&S[i]); //fa una unlock sulla sua mutex, così che quando va a fare la down nella funzione take_forks(i) essa non verrà bloccata e quindi potrà mangiare
}
}
int Right(int i) /*Nome filosofa di destra*/
{
return (i-1) % N;
}
int Left(int i) /*Di sinistra */
{
return (i+1) % N;
}
Per riassumere.
Una filosofa quando smette di pensare sarà affamata, per poter mangiare dovrà avere le due filosofe ai fianchi che non stiano mangiando, altrimenti aspetterà fino a quando non saranno disponibili le forchette.
Il problema delle cinque filosofe è molto utile per risolvere problemi dove dei processi devono competere per l'accesso a un numero limitato di risorse, come i dispositivi di input/output.
Il secondo problema invece riguarda quello di un barbiere che ha una sola postazione per il taglio dei capelli e un numero di sedie per far attendere i clienti. Se non c'è nessun cliente nel negozio il barbiere inizierà a dormire. Quando arriverà un cliente si sveglierà e inizierà a tagliargli i capelli; se intanto arrivano altri clienti mentre il barbiere è occupato essi attenderanno sulle sedie, se un cliente vuole fare il taglio dei capelli ma ci sono tutte le sedie per attendere occupato questo se ne va.
Il codice è il seguente:
#define SEDIE 5 //Il numero di sedie utilizzate per far aspettare i clienti
typedef int semaforo; //La nostra variabile semaforo
semaforo clienti = 0; //Numero dei clienti in attesa
semaforo barbieri = 0; //Numero dei barbieri in attesa dei clienti
semaforo mutex = 1; //Mutex che utilizziamo per la mutua esclusione
int in_attesa = 0; //Numero dei clienti in attesa (non durante il taglio dei capelli)
void barbiere(void)
{
while(1)
{
down(&clienti); //Il barbiere inizierà a dormire se non c'è nessun cliente
down(&mutex); //Entra nella sezione critica perché va a modificare una variabile globale ...
in_attesa = in_attesa -1; //... che è in_attesa
up(&barbieri); //Un barbiere è pronto per tagliare i capelli al cliente
up(&mutex); //Esco dalla zona critica
taglia_capelli(); //Taglio i capelli al cliente
}
}
void cliente(void)
{
down(&mutex); //Entra nella zona critica
if(in_attesa < SEDIE) //Se i clienti in attesa sono meno delle sedie per attendere...
{
in_attesa = in_attesa +1; //...anche questo cliente si metterà in attesa
up(&clienti); //Incrementa in numero di clienti in attesa
up(&mutex); //Esce dalla zona critica
down(&barbieri); //Blocca l'esecuzione del cliente se non c'è nessun barbiere disponibile per il taglio dei capelli
vai_al_taglio(); //Taglio dei capelli
}
else //Se tutte le sedie sono occupate
{
up(&mutex); // Allora esco direttamente dalla zona critica, senza eseguire il codice che mi serve
}
}
Per fare un esempio, questo problema è ricorrente nei server web.
Ogni volta che un utente vorrà andare su una pagina web il server gli invierà il file .html della pagina corrispondente tramite un codice simile a quello del barbiere. Il server infatti avrà naturalmente più di un "barbiere" che potrà servire i vari utenti (perché se potesse inviare la pagina ad un solo utente alla volta sarebbe improponibile, basti pensare a pagine molto visitate) che richiedono la pagina internet; per gestire i più barbieri semplicemente li creerà come thread da un thread master. Il thread master sarà quello in ascolto sulla porta 80, cioè quella che riguarda le pagine web; ogni volta che un cliente arriva darà la richiesta del cliente al thread barbiere, rimettendosi immediatamente in ascolto per nuove richieste.
Viene eseguito un deposito da parte del primo thread, che va ad aggiornare il bilancio.
Il secondo thread non avrà il bilancio aggiornato (1200€) ma avrà il bilancio letto in precedenza (1000€), quindi quando si andrà ad aggiornare il valore si avrà solo l'ultima modifica fatta dal secondo thread e non quella fatta dal primo.
Questo esempio è uno dei tanti e fa capire quanto sia importante la gestione di thread o processi quando devono andare a scrivere su dei dati condivisi.
Per risolvere questo problema utilizziamo le variabili mutex o le variabili semaforo.
Nella gestione di più processi o thread che eseguono delle corse critiche abbiamo 3 problemi ricorrenti, che sono i seguenti:
- Le cinque filosofe a tavola
- Il barbiere che dorme
- Lettore e scrittore
- Produttore e consumatore
Il primo problema è il seguente: 5 filosofe sono ad un tavolo rotondo per mangiare degli spaghetti. Ogni filosofa dispone di una sola forchetta ma sfortunatamente gli spaghetti sono così scivolosi che per mangiarli bisogna utilizzare per forza 2 forchette insieme.
Le nostre filosofe (cioè i nostri processi) possono essere in 3 stati distinti: pensante , affamata e mangiante .
Come possiamo far mangiare il maggior numero di filosofe contemporaneamente senza creare nessuna situazione di stallo?
Utilizzeremo un vettore di interi state di un numero uguale a quello delle filosofe; in ognuno di queste posizioni andremo ad inserire lo stato della nostra filosofa (pensante, affamata e mangiante).
In totale avremo 6 mutex: 5 per ogni filosofa mentre ne avremo una globale.
Ogni filosofa può iniziare a mangiare solo se nessuna delle 2 filosofe ai suoi fianchi non sta mangiando.
Il codice per risolvere questo problema è il seguente:
#define N 5
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef int semaphore;
int state[N];
semaphore Mutex=1; /*Master Mutex*/
semaphore S[N]; /*Philosopher's Mutex, Inizializzato come? tutto a zero, ovvero bloccato*/
/*down circa= lock. up circa= unlock. */
void philosopher(int i) //Codice eseguito dalle 5 filosofe
{
while (1)
{
think(); // La filosofa sta pensando
take_forks(i); // la filosofa è affamata, quindi cercherà di prendere le forchette
eat(); // entra nella sezione critica, inizia a mangiare
put_forks(i); // posa le forchette e ricomincia a pensare
}
}
void take_forks(int i) //Funzione prendi forchette
{
down(&Mutex); //Cerca di entrare nella zona critica
state[i]=HUNGRY; //Si modifica il suo stato rendendolo da pensante ad affamata
test(i); // controlla che le forchette siano disponibili, quindi che le due filosofe da parte non stiano mangiando
up(&Mutex); //Esce dalla regione critica
down(&S[i]); // Se la funzione test è andata a buon fine, quindi la filosofa ha ottenuto le forchette essa inizierà a mangiare, altrimenti si bloccherà aspettando che una delle filosofe ai suoi lati la sblocchi, così che possa iniziare a mangiare
}
void put_forks(int i) //Funzione posa forchette
{
down (&Mutex); //Cerca di entrare nella zona critica
state[i]=THINKING; //Ha finito di mangiare, quindi il suo stato viene modificato in pensante
test( Left(i) ) ; // Controlla che la filosofa alla sua sinistra possa mangiare
test( Right(i) ); // Controlla che la filosofa alla sua destra possa mangiare
up(&Mutex); //Esce dalla regione critica
}
void test(i) //Funzione che controlla che le filosofe possano iniziare a mangiare
{
if(state[i]== HUNGRY && state[ Left(i) ]!=EATING && state[ Right(i) ]!=EATING )
//Se la filosofa è affamata e nessuna delle filosofe vicine sta mangiando, allora la filosofa può mangiare
{
state[i]=EATING; // Si mette nello stato mangiante
up(&S[i]); //fa una unlock sulla sua mutex, così che quando va a fare la down nella funzione take_forks(i) essa non verrà bloccata e quindi potrà mangiare
}
}
int Right(int i) /*Nome filosofa di destra*/
{
return (i-1) % N;
}
int Left(int i) /*Di sinistra */
{
return (i+1) % N;
}
Per riassumere.
Una filosofa quando smette di pensare sarà affamata, per poter mangiare dovrà avere le due filosofe ai fianchi che non stiano mangiando, altrimenti aspetterà fino a quando non saranno disponibili le forchette.
Il problema delle cinque filosofe è molto utile per risolvere problemi dove dei processi devono competere per l'accesso a un numero limitato di risorse, come i dispositivi di input/output.
Il secondo problema invece riguarda quello di un barbiere che ha una sola postazione per il taglio dei capelli e un numero di sedie per far attendere i clienti. Se non c'è nessun cliente nel negozio il barbiere inizierà a dormire. Quando arriverà un cliente si sveglierà e inizierà a tagliargli i capelli; se intanto arrivano altri clienti mentre il barbiere è occupato essi attenderanno sulle sedie, se un cliente vuole fare il taglio dei capelli ma ci sono tutte le sedie per attendere occupato questo se ne va.
Il codice è il seguente:
#define SEDIE 5 //Il numero di sedie utilizzate per far aspettare i clienti
typedef int semaforo; //La nostra variabile semaforo
semaforo clienti = 0; //Numero dei clienti in attesa
semaforo barbieri = 0; //Numero dei barbieri in attesa dei clienti
semaforo mutex = 1; //Mutex che utilizziamo per la mutua esclusione
int in_attesa = 0; //Numero dei clienti in attesa (non durante il taglio dei capelli)
void barbiere(void)
{
while(1)
{
down(&clienti); //Il barbiere inizierà a dormire se non c'è nessun cliente
down(&mutex); //Entra nella sezione critica perché va a modificare una variabile globale ...
in_attesa = in_attesa -1; //... che è in_attesa
up(&barbieri); //Un barbiere è pronto per tagliare i capelli al cliente
up(&mutex); //Esco dalla zona critica
taglia_capelli(); //Taglio i capelli al cliente
}
}
void cliente(void)
{
down(&mutex); //Entra nella zona critica
if(in_attesa < SEDIE) //Se i clienti in attesa sono meno delle sedie per attendere...
{
in_attesa = in_attesa +1; //...anche questo cliente si metterà in attesa
up(&clienti); //Incrementa in numero di clienti in attesa
up(&mutex); //Esce dalla zona critica
down(&barbieri); //Blocca l'esecuzione del cliente se non c'è nessun barbiere disponibile per il taglio dei capelli
vai_al_taglio(); //Taglio dei capelli
}
else //Se tutte le sedie sono occupate
{
up(&mutex); // Allora esco direttamente dalla zona critica, senza eseguire il codice che mi serve
}
}
Per fare un esempio, questo problema è ricorrente nei server web.
Ogni volta che un utente vorrà andare su una pagina web il server gli invierà il file .html della pagina corrispondente tramite un codice simile a quello del barbiere. Il server infatti avrà naturalmente più di un "barbiere" che potrà servire i vari utenti (perché se potesse inviare la pagina ad un solo utente alla volta sarebbe improponibile, basti pensare a pagine molto visitate) che richiedono la pagina internet; per gestire i più barbieri semplicemente li creerà come thread da un thread master. Il thread master sarà quello in ascolto sulla porta 80, cioè quella che riguarda le pagine web; ogni volta che un cliente arriva darà la richiesta del cliente al thread barbiere, rimettendosi immediatamente in ascolto per nuove richieste.
Nessun commento:
Posta un commento