Anteprima
Vedrai una selezione di 10 pagine su 196
Sistemi Operativi - Appunti Pag. 1 Sistemi Operativi - Appunti Pag. 2
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 6
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 11
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 16
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 21
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 26
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 31
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 36
Anteprima di 10 pagg. su 196.
Scarica il documento per vederlo tutto.
Sistemi Operativi - Appunti Pag. 41
1 su 196
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

ESERCIZIO:

Vediamo adesso di fare un esempio, per chiarire l'uso dei mutex. Presentiamo prima un programma, che

non utilizza i mutex, in cui vengono creati due thread che lavorano su una variabile intera condivisa.

Presentiamo una prima versione di tale programma, priva di sincronizzazione, proprio per evidenziare i

problemi che si possono avere quando più thread operano senza controllo su una risorsa condivisa,

ricordando che tali problemi scaturiscono dal fatto che la schedulazione dei thread avviene in una maniera

imprevedibile. *************** file: thread3.c ***************

/*lanciando l'eseguibile relativo a questo listato verrà stampato sullo standard output 22 volte il valore della

x. In particolare quello che dovrebbe accadere (se i thread fossero sincronizzati) è che i valori della x

dovrebbero andare da 0 a 10 in maniera crescente e dovrebbero essere ripetuti:

x = 0

x = 0

x = 1

x = 1

x = 2

x = 2

...

invece, proprio perché i thread non sono sincronizzati, i valori della x risultano essere ripetuti e male

intercalati, all'inizio viene stampato 2 volte lo stesso valore, alla fine potrebbe capitare che vengano stampati

dapprima valori maggiori e poi valori minori! */

#include <pthread.h>

#include<stdio.h>

int x = 0; /* Variabile condivisa, dichiarata globalmente! */

void *thread_func1(void *arg1); //prototipo della thread function

int main (void)

{ int res1, res2; //dichiaro 2 interi in cui memorizzo il valore di ritorno delle pthread_create

pthread_t IDthread1, IDthread2; //dichiaro 2 ptatori a thread

res1 = pthread_create(&IDthread1, NULL, thread_func1, NULL); //creo il primo thread

if (res1 != 0) //eseguo il controllo sulla creazione del thread

{

printf("Creazione thread1 fallita!!!\n");

return -1;

}

res2 = pthread_create(&IDthread2, NULL, thread_func1, NULL); //creo il 2° thread

if (res2 != 0)

{ printf("Creazione thread2 fallita!!!\n");

return -1;

}

pthread_join(IDthread1, NULL); //il main aspetta il primo thread

pthread_join(IDthread2, NULL); //il main aspetta il 2° thread

return (0);

} //fine funzione main

void *thread_func1(void *arg1) /*la thread function non prende in ingresso alcun parametro, tanto è vero

{ che al suo interno non viene effettuato nessun

casting*/ int i; //dichiaro un intero

long int j; //dichiaro un long int che mi serve x realizzare l'attesa mediante il for

for(i=0; i < 11; i++)

{ x = i; //questa variabile essendo dichiarata globalmente è comune ai 2 thread

for (j=0; j < 9000000; j++); //questo for serve solo x realizzare un ritardo nella

stampa! printf("x = %d\n", x); //stampo x!

}

pthread_exit(0); //il thread chiama la exit, informando il main di aver terminato!

}

eseguendo questo programma l'output sarà il seguente:

marco@ubuntu:~/Documenti/Università/Es_Sistemi_operativi/Semafori/lab_20_aprile$

x = 0

x = 1

x = 1

x = 2

x = 2

x = 3

x = 3

x = 4

x = 4

x = 5

x = 5

x = 6

x = 6

x = 7

x = 7

x = 8

x = 8

x = 9

x = 10

x = 9

x = 9

x = 10

Vediamo, adesso la versione dello stesso programma, in cui l'accesso alla variabili condivisa x è controllata

con l'ausilio di un semaforo mutex:

*************** file: Thread4.c ***************

#include <pthread.h>

#include<stdio.h>

int x = 0; /* Variabile condivisa, dichiarata globalmente! */

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /*dichiaro un semaforo globalmente, in modo

che

entrambi i thread lo possano vedere*/

void *thread_func1(void *arg1); //prototipo della thread function

int main (void)

{ int res1, res2;

pthread_t IDthread1, Idthread2;

res1 = pthread_create(&IDthread1, NULL, thread_func1, NULL); //creo il primo thread

if (res1 != 0)

{ printf("Creazione thread1 fallita!!!\n");

return -1;

}

res2 = pthread_create(&IDthread2, NULL, thread_func1, NULL); //creo il 2° thread

if (res2 != 0)

{ printf("Creazione thread2 fallita!!!\n");

return -1;

}

pthread_join(IDthread1, NULL); //il main aspetta il termine del primo thread

pthread_join(IDthread2, NULL); //il main aspetta il termine del secondo thread

return (0);

}

void *thread_func1(void *arg1) //al solito questa funzione non prende parametri in ingresso

{ int i;

long int j;

pthread_mutex_lock(&mutex); //prima di incrementare la x blocco il semaforo

/* è sempre consigliabile che la parte compresa tra il lock e l'unlock sia molto piccola e sia limitata solo alle

parti di codice in cui effettivamente c'è una manipolazione sulle variabili condivise!!!! */

for(i=0; i < 11; i++)

{ x = i; //pongo x = i

for (j=0; j < 9000000; j++); //attende

printf("x = %d\n", i); //stampo la x

}

pthread_mutex_unlock(&mutex); //sblocco il semaforo

pthread_exit(0); //il thread esce!

}

l'output del programma è il seguente:

marco@ubuntu:~/Documenti/Università/Es_Sistemi_operativi/Semafori/lab_20_aprile $ ./es4

x = 0

x = 1

x = 2

x = 3

x = 4

x = 5

x = 6

x = 7

x = 8

x = 9

x = 10

x = 0

x = 1

x = 2

x = 3

x = 4

x = 5

x = 6

x = 7

x = 8

x = 9

x = 10

eseguendo più volte lo stesso programma l'output sarà sempre lo stesso, a differenza del precedente, che

cambierà ad ogni esecuzione, in quanto diverso sarà di volta in volta lo scheduling effettuato sui thread!

Semafori generalizzati per i thread

Abbiamo presentato i semafori binari (mutex), passiamo adesso a vedere i semafori generalizzati.

Un semaforo ci fornisce metodi utili per ottenere questo meccanismo. Un semaforo sostanzialmente è un

contatore che può essere utilizzato per sincronizzare molti thread. Con i semafori generalizzati possiamo

ottenere la mutua esclusione cosi come abbiamo visto con i mutex, evitando quindi race conditions.

Ogni contatore di semaforo ha un valore, che deve essere un intero non negativo.

Nell’esempio precedente della coda in cui i job venivano processati dai thread c’è un problema: quando la

coda si svuota i thread cessano di esistere, quindi, o riempiamo preventivamente la coda, aggiungendo

processi più velocemente di quanto vengano processati, oppure possiamo implementare un meccanismo per

bloccare i threads da quando la coda si svuota fino a che nuovi jobs vengono inseriti.

Un semaforo generalizzato ci fornisce metodi utili per ottenere questo meccanismo.

Le operazioni che possono essere effettuate su un semaforo sono: Wait e Post.

Ogni semafororo deve essere assegnato ad una risorsa condivisa.

L'operazione di Wait va utilizzata nel momento in cui un thread deve accedere alla risorsa condivisa, quindi

testa il semaforo, mediante appunto l'operazione di Wait. Quello che accade è che, mediante tale

operazione, il contatore del semaforo viene decrementato di uno.

Si possono verificare però 2 casi:

1. il contatore è positivo: significa che quella risorsa è disponibile, quindi il thread decrementa di una

unità il contatore e continua la sua esecuzione, ciò significa che il thread starà impegnando una

risorsa che, quindi non sarà più disponibile, e potrà continuare nell'esecuzione delle sue operazioni.

2. Il contatore è 0: ciò significa che tutte le risorse condivise sono impegnate da altri thread, per cui

quello che succede è che il thread che ha effettuato la chiamata viene bloccato fino a che il

semaforo non torna positivo (a causa di qualche altro thread). Quando il semaforo tornerà positivo il

thread verrà risvegliato, gli verrà assegnata la risorsa e decrementato di una unità il valore del

semaforo.

L'operazione di Post, invece, consente al thread di rendere disponibile la risorsa che aveva

precedentemente impegnato, incrementando il contatore. Cioè Il thread rilascia il lock che aveva acquisito

dando la possibilità ad altri threads di ottenere il lock su quella specifica risorsa monitorata dal semaforo.

Ovviamente queste due operazioni sono svolte in maniera atomica!

GNU/Linux ci fornisce due differenti implementazioni leggermente differenti di semafori.

Quella che vedremo adesso è l’implementazione dello standard POSIX, e si usa quando bisogna fare

comunicare tra loro i threads. L’altra implementazione si utilizza per la comunicazione fra processi.

Per utilizzare un semaforo bisogna includere la libreria <semaphore.h>.

Un semaforo generalizzato viene rappresentato da una variabile del tipo sem_t, e prima di utilizzarla

dovremo inizializzarla mediante la chiamata sem_init passandole un puntatore alla variabile sem_t.

Il prototipo di tale funzione è il seguente:

int sem_init(sem_t *sem, int type, int val)

dove sem è il puntatore al semaforo da inizializzare; type indica se il semaforo è condiviso tra processi (in

linux tale opzione non viene implementata) e val è il valore iniziale del semaforo.

Per cui il secondo parametro deve essere zero ed il terzo il valore iniziale che vogliamo attribuire al

semaforo.

Tale funzione ritorna 0 se l'operazione è andata a buon fine, altrimenti ritorna -1,e viene settata la variabile

errno.

Se utilizziamo il semaforo per poco tempo è buona norma deallocarlo mediante la chiamata sem_destroy. Il

prototipo di tale funzione è: int sem_destroy(sem_t *sem)

dove sem è il puntatore al semaforo generalizzato. In seguito a questa chiamata le risorse associate al

semaforo sem vengono liberate. È possibile distruggere solamente un semaforo che è stato inizializzato

mediante la funzione sem_init(). Se si distrugge un semaforo, mediante questa chiamata, mentre altri

processi o thread ne detengono il lock produrrà un comportamento del programma non definito.

La funzione sem_destroy ritorna 0 se l'operazione va a buon fine altrimenti torna -1 e viene settata la

variabile errno.q

Le operazioni di Wait e Post si invocano con le chiamate: sem_wait e sem-post.

Wait

Il prototipo della funzione wait è: int sem_wait(sem_t *sem)

Questa funzione decrementa il valore del contatore associato al semaforo, di cui passiamo il puntatore.

Nell'ipotesi in cui il semaforo ha un valore positivo, ritorna immediatamente e l'esecuzione del thread non

viene interrotta, in caso contrario sospende il thread fino a quando qualche altro thread non rilascia una

risorsa. Anche questa funzione torna 0 in caso di successo e -1 in caso di insuccesso. Questa operazione

viene svolta in modo atomico dal sistema.

Post

Il prototipo della funzione Post è: int sem_post(sem_t *sem)

Questa funzione incrementa il valore del contatore associato al semaforo, di cui passiamo il puntatore. S

Dettagli
Publisher
A.A. 2013-2014
196 pagine
2 download
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher MarcoCarbone di informazioni apprese con la frequenza delle lezioni di Sistemi operativi e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli Studi di Messina o del prof Scarpa Marco.