Sincronizzazione (join, mutex, condition), semaphore - Lucidi

iet.unipi.it

Sincronizzazione (join, mutex, condition), semaphore - Lucidi

Sistemi Operativi

Corso di Laurea Triennale

in Ingegneria Informatica

Esercitazione 8

• Thread:

• Join

• Mutex

• Condition

• Semaphore

daniel.cesarini@for.unipi.it

1


Domande sulle

lezioni passate


Sommario – esercitazione 8

Thread Posix – sincronizzazione

Join

Semafori

Variabili condition

Semafori generali (FORSE)


Sincronizzazione 3


Sincronizzazione

(semplice)


Join tra thread

Forma elementare di sincronizzazione

il thread che effettua il join si blocca

finché uno specifico thread non termina

il thread che effettua il join può ottenere lo

stato del thread che termina

Attributo detachstate di un thread

specifica se si può invocare o no la

funzione join su un certo thread

un thread è joinable per default

Sincronizzazione 5


Operazione di join

int pthread_join( pthread_t *thread, void **value )




pthread_t *thread

identificatore del thread di cui attendere la

terminazione

void **value

valore restituito dal thread che termina

Valore di ritorno

0 in caso di successo

EINVAL se il thread da attendere non è joinable

ERSCH se non è stato trovato nessun thread

corrispondente all’identificatore specificato

Sincronizzazione 6


Impostazione attributo di join

(1 di 4)

int pthread_attr_init( pthread_attr_t *attr )

Inizializza gli attributi del pthread

int pthread_attr_destroy ( pthread_attr_t *attr)

Dealloca il pthread

Sincronizzazione 7


Impostazione attributo di join

(2 di 4)

Un thread può essere:

Joinable: i thread non sono rilasciati

automaticamente ma rimangono come zombie

finchè altri thread non effettuano delle join

Detached: i thread detached sono rilasciati

automaticamente e non possono essere oggetto

di join da parte di altri thread.

int pthread_attr_setdetachstate( pthread_attr_t *attr,

int detachstate )


Detach può essere:

PTHREAD_CREATE_DETACHED

PTHREAD_CREATE_JOINABLE.

Sincronizzazione 8


Impostazione attributo di join

(3 di 4)

/* Attributo */

pthread_attr_t attr;

/* Inizializzazione esplicita dello stato joinable */

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

...

pthread_attr_destroy(&attr);

Sincronizzazione 9


Impostazione attributo di join

(4 di 4)

int main (int argc, char *argv[]) {

pthread_t thread[NUM_THREADS];

...

pthread_attr_destroy(&attr);

for(t=0; t


Esempio 3: thread join (1 di 3)

/* Include */

#include

#include

#include

#define NUM_THREADS 5

void *PrintHello(void *num) {

printf("\n%d: Hello World!\n", num);

pthread_exit(NULL);

}

Continua

Sincronizzazione 11


Esempio 3: thread join (2 di 3)

int main (int argc, char *argv[]) {

pthread_t threads[NUM_THREADS];

void *status;

int rc, t;

pthread_attr_t attr;

/* Inizializzazione esplicita dello stato joinable */

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

for(t=0; t


Esempio 3: thread join (3 di 3)

for(t=0; t


Semafori


Cosa sono i semafori?

I semafori sono primitive fornite dal

sistema operativo per permettere la

sincronizzazione tra processi e/o

thread.

Sincronizzazione 15


Operazioni sui semafori

In genere sono tre le operazioni che vengono

eseguite da un processo su

un semaforo:

Create: creazione di un semaforo.

Wait: attesa su di un semaforo dove si verifica il

valore del semaforo

while(sem_value


Semafori di mutua esclusione


Cosa sono i mutex? (1 di 2)

Una variabile mutex è una variabile

che serve per la protezione delle

sezioni critiche:

variabili condivise modificate da più

thread

solo un thread alla volta può accedere ad

una risorsa protetta da un mutex

Il mutex è un semaforo binario cioè il

valore può essere 0 (occupato) oppure

1 (libero)

Sincronizzazione 18


Cosa sono i mutex? (2 di 2)

Pensiamo ai mutex come a delle

serrature:

il primo thread che ha accesso alla coda

dei lavori lascia fuori gli altri thread fino a

che non ha portato a termine il suo

compito.

I threads piazzano un mutex nelle

sezioni di codice nelle quali vengono

condivisi i dati.

Sincronizzazione 19


Garantire la Mutua Esclusione (1

di 2)

Due thread devono decrementare il valore di

una variabile globale data se questa è

maggiore di zero

data = 1

THREAD1

THREAD2

if(data>0)

if(data>0)

data --; data --;

Sincronizzazione 20


Garantire la Mutua Esclusione (2

di 2)

A seconda del tempo di esecuzione dei due thread, la

variabile data assume valori diversi.

Data THREAD1 THREAD2

1 if(data>0)

1 data --;

0 if(data>0)

0 data --;

0 = valore finale di data

--------------------------------------------------------

1 if(data>0)

1 if(data>0)

1 data --;

0 data --;

-1 = valore finale di data

Sincronizzazione 21


Uso dei mutex

Creare e inizializzare una variabile mutex

Più thread tentano di accedere alla risorsa

invocando l’operazione di lock

Un solo thread riesce ad acquisire il mutex

mentre gli altri si bloccano

Il thread che ha acquisito il mutex manipola

la risorsa

Lo stesso thread la rilascia

invocando la unlock

Un altro thread acquisisce il mutex e così via

Distruzione della variabile mutex

Sincronizzazione 22


Creazione mutex


Per creare un mutex è necessario usare

una variabile di tipo pthread_mutex_t

contenuta nella libreria pthread

pthread_mutex_t è una struttura che

contiene:

Nome del mutex

Proprietario

Contatore

Struttura associata al mutex



La coda dei processi sospesi in attesa che mutex sia

libero.

… e simili

Sincronizzazione 23


Inizializzazione mutex

statica

contestuale alla dichiarazione

dinamica

attraverso

pthread_mutex_t mutex;

pthread_mutex_init (&mutex, NULL);

Sincronizzazione 24


Inizializzazione statica


Per il tipo di dato pthread_mutex_t, è definita la

macro di inizializzazione

PTHREAD_MUTEX_INITIALIZER


Il mutex è un tipo definito "ad hoc" per gestire la

mutua esclusione quindi il valore iniziale può

essergli assegnato anche in modo statico mediante

questa macro.

/* Variabili globali */

pthread_mutex_t amutex = PTHREAD_MUTEX_INITIALIZER;

Sincronizzazione 25


Inizializzazione dinamica

pthread_mutex_t mutex;

int pthread_mutex_init( pthread_mutex_t *mutex, const

pthread_mutexattr_t *mattr )




pthread_mutex_t *mutex

puntatore al mutex da inizializzare

pthread_mutexattr_t *mattr

attributi del mutex da inizializzare

se NULL usa valori default

Valore di ritorno

sempre il valore 0

Sincronizzazione 26


Interfacce

Su mutex sono possibili solo due

operazioni: locking e unlocking

(equivalenti a wait e signal sui

semafori)

Sincronizzazione 27


Interfaccia: Lock

Ogni thread, prima di accedere ai dati

condivisi, deve effettuare la lock su una

stessa variabile mutex.

Blocca l’accesso da parte di altri thread.

Se più thread eseguono l’operazione di lock

su una stessa variabile mutex, solo uno dei

thread termina la lock e prosegue

l’esecuzione, gli altri rimangono bloccati

nella lock. In tal modo, il processo che

continua l’esecuzione può accedere ai dati

(protetti mediante la mutex).

Sincronizzazione 28


Operazioni: lock e trylock

lock

bloccante (standard)

trylock

non bloccante (utile per evitare deadlock)

è come la lock() ma se si accorge che il mutex è

già in possesso di un altro thread (e quindi si

rimarrebbe bloccati) restituisce immediatamente

il controllo al chiamante con risultato EBUSY

Una situazione di deadlock si verifica quando uno o più thread sono

bloccati aspettando un evento che non si verificherà mai.

Sincronizzazione 29


lock

int pthread_mutex_lock( pthread_mutex_t *mutex )

pthread_mutex_t *mutex

puntatore al mutex da bloccare

Valore di ritorno

0 in caso di successo

diverso da 0 altrimenti

Sincronizzazione 30


trylock

int pthread_mutex_trylock( pthread_mutex_t *mutex )



pthread_mutex_t *mutex

puntatore al mutex da bloccare

Valore di ritorno

0 in caso di successo e si ottenga la proprietà

della mutex

EBUSY se il mutex è occupato

Sincronizzazione 31


Interfaccia: Unlock

Libera la variabile mutex.

Un altro thread che ha

precedentemente eseguito la lock

della mutex potrà allora terminare la

lock ed accedere a sua volta ai dati.

Sincronizzazione 32


unlock

int pthread_mutex_unlock( pthread_mutex_t *mutex )

pthread_mutex_t *mutex

puntatore al mutex da sbloccare

Valore di ritorno

0 in caso di successo

Sincronizzazione 33


destroy

int pthread_mutex_destroy( pthread_mutex_t *mutex )

Elimina il mutex

pthread_mutex_t *mutex

puntatore al mutex da distruggere

Valore di ritorno

0 in caso di successo

EBUSY se il mutex è occupato

Sincronizzazione 34


Esempio 4: uso dei mutex (1 di 2)

#include

int a=1, b=1;

pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

void* thread1(void *arg) {

pthread_mutex_lock(&m);

printf(“Primo thread (parametro: %d)\n", *(int*)arg);

a++; b++;

pthread_mutex_unlock(&m);

}

void* thread2(void *arg) {

pthread_mutex_lock(&m);

printf(“Secondo thread (parametro: %d)\n", *(int*)arg);

b=b*2; a=a*2;

pthread_mutex_unlock(&m);

} Continua


Sincronizzazione 35


Esempio 4: uso dei mutex (2 di 2)

main() {

pthread_t threadid1, threadid2;

int i = 1, j=2;

pthread_create(&threadid1, NULL, thread1, (void *)&i);

pthread_create(&threadid2, NULL, thread2, (void *)&j);

pthread_join(threadid1, NULL);

pthread_join(threadid2, NULL);

printf("Valori finali: a=%d b=%d\n", a, b);

}

Tratto con modifiche da:

http://www.univ.trieste.it/~mumolo/posix2.p

df

Sincronizzazione 36


Esempio 5: inizializzazione dinamica

(1 di 2)

#include

int a=1, b=1;

pthread_mutex_t m;

void* thread1(void *arg) {

pthread_mutex_lock(&m);

printf(“Primo thread (parametro: %d)\n", *(int*)arg);

a++; b++;

pthread_mutex_unlock(&m);

}

void* thread2(void *arg) {

pthread_mutex_lock(&m);

printf(“Secondo thread (parametro: %d)\n", *(int*)arg);

b=b*2; a=a*2;

pthread_mutex_unlock(&m);

} Continua


Sincronizzazione 37


Esempio 5: inizializzazione dinamica

(2 di 2)

main() {

pthread_t threadid1, threadid2;

int i = 1, j=2;

pthread_mutex_init(&m, NULL);

pthread_create(&threadid1, NULL, thread1, (void *)&i);

pthread_create(&threadid2, NULL, thread2, (void *)&j);

pthread_join(threadid1, NULL);

pthread_join(threadid2, NULL);

printf("Valori finali: a=%d b=%d\n", a, b);

pthread_mutex_destroy(&m);

}

Sincronizzazione 38


Esempio 6 (1 di 3)

/* esempio utilizzo dei Mutex */

#include

#include

pthread_mutex_t mymutex;

void *body(void *arg){

int i,j;

for (j=0; j


Esempio 6 (2 di 3)

int main(){

pthread_t t1,t2,t3;

pthread_attr_t myattr;

int err;

pthread_mutexattr_t mymutexattr;

pthread_mutexattr_init(&mymutexattr);

pthread_mutex_init(&mymutex, &mymutexattr);

pthread_mutexattr_destroy(&mymutexattr);

pthread_attr_init(&myattr);


Continua


Sincronizzazione 40


Esempio 6 (3 di 3)

err = pthread_create(&t1, &myattr, body, (void *)".");

err = pthread_create(&t2, &myattr, body, (void *)"#");

err = pthread_create(&t3, &myattr, body, (void *)"o");

pthread_attr_destroy(&myattr);

}

pthread_join(t1, NULL);

pthread_join(t2, NULL);

pthread_join(t3, NULL);

printf("\n");

return 0;

Sincronizzazione 41


Esercizio 3

pthreads-3a-mutex.c

analizzare l'output

modificare in modo da ottenere un

funzionamento corretto

pthreads-3b-mutex.c

soluzione dell'esercizio precedente

Sincronizzazione 42


pthreads-3a-mutex.c

#include

#include

#define NUM_THREADS 40

int shared = 0;

void *thread_main(void* arg){

int i,k;

for (i=0; i


Variabili condition


Condition vs Semafori

Le variabili condition sono molto diverse dai

semafori di sincronizzazione, anche se

semanticamente fanno la stessa cosa

Le primitive delle condition si preoccupano di

rilasciare la mutua esclusione prima di

bloccarsi e ed riacquisirla dopo essere state

sbloccate

I semafori generali, invece, prescindono dalla

presenza di altri meccanismi

Sincronizzazione 45


Cosa sono le variabili condition

Strumento di sincronizzazione: consente

la sospensione dei thread in attesa che sia

soddisfatta una condizione logica.

Una condition variable è utilizzata per

sospendere l'esecuzione di un thread in

attesa che si verifichi un certo evento.

Ad ogni condition viene associata una coda

per la sospensione dei thread.

La variabile condizione non ha uno stato,

rappresenta solo una coda di thread.

Sincronizzazione 46


Variabili condition

Attraverso le variabili condition è

possibile implementare condizioni più

complesse che i thread devono

soddisfare per essere eseguiti.

Linux garantisce che i thread bloccati

su una condizione vengano sbloccati

quando essa cambia.

Sincronizzazione 47


Mutua esclusione

Una variabile condizione non fornisce

la mutua esclusione.

C'è bisogno di un mutex per poter

sincronizzare l'accesso ai dati.

Sincronizzazione 48


Sincronizzazione

Una variabile condition è sempre associata

ad un mutex

un thread ottiene il mutex e testa il predicato

se il predicato è verificato allora il thread

esegue le sue operazioni e rilascia il mutex

se il predicato non è verificato, in modo

atomico

il mutex viene rilasciato (implicitamente)

il thread si blocca sulla variabile condition

un thread bloccato riacquisisce il mutex nel

momento in cui viene svegliato da un altro

thread

Sincronizzazione 49


Creazione condition

Oggetti di sincronizzazione su cui un

processo si può bloccare in attesa

associate ad una condizione logica

arbitraria

generalizzazione dei semafori

nuovo tipo pthread_cond_t

attributi variabili condizione di tipo

pthread_condattr_t

Sincronizzazione 50


Inizializzazione statica

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

• Per il tipo di dato pthread_cond_t, è definita

la macro di inizializzazione

PTHREAD_COND_INITIALIZER

Sincronizzazione 51


Inizializzazione dinamica

int pthread_cond_init( pthread_cond_t *cond,

pthread_condattr_t *cond_attr )



pthread_cond_t *cond

puntatore ad un’istanza di condition che

rappresenta la condizione di sincronizzazione

pthread_condattr_t *cond_attr

punta a una struttura che contiene gli attributi

della condizione

se NULL usa valori di default

Sincronizzazione 52


Distruzione variabili condition

int pthread_cond_destroy( pthread_cond_t *cond )

Dealloca tutte le risorse allocate per gestire la

variabile condizione specificata

Non devono esistere thread in attesa della

condizione

pthread_cond_t *cond

puntatore ad un’istanza di condition da distruggere

Valore di ritorno

0 in caso di successo oppure un codice d’errore ≠ 0

Sincronizzazione 53


Interfacce

Operazioni fondamentali:

wait (sospensione)

signal ( risveglio)

Sincronizzazione 54


Interfaccia wait

La wait serve per sincronizzarsi con una certa

condizione all'interno di un blocco di dati condivisi e

protetti da un mutex

La presenza del mutex fra i parametri garantisce

che, al momento del bloccaggio, esso venga

liberato, eliminando a monte possibili errori di

programmazione che potrebbero condurre a

condizioni di deadlock.

Se la wait ritorna in modo regolare, è garantito che

la mutua esclusione, sul semaforo mutex passatole,

sia stata nuovamente acquisita.

Sincronizzazione 55


wait

int pthread_cond_wait( pthread_cond_t *cond,

pthread_mutex_t *mutex )




pthread_cond_t *cond

puntatore ad un’istanza di condition che

rappresenta la condizione di sincronizzazione

puntatore all’oggetto condizione su cui bloccarsi

pthread_mutex_t *mutex

l'indirizzo di un semaforo di mutua esclusione

necessario alla corretta consistenza dei dati

Valore di ritorno

sempre 0

Sincronizzazione 56


Interfaccia signal

La signal non si preoccupa di liberare la

mutua esclusione, infatti, fra i suoi parametri

non c'è il mutex

Il mutex deve essere rilasciato

esplicitamente, altrimenti si potrebbe

produrre una condizione di deadlock.

Due varianti

Standard: sblocca un solo thread bloccato

Broadcast: sblocca tutti i thread bloccati

Sincronizzazione 57


signal

int pthread_cond_signal ( pthread_cond_t *cond)

Se esistono thread sospesi nella coda associata a

cond, viene risvegliato il primo.

Se non vi sono thread sospesi sulla condizione, la

signal non ha effetto.

pthread_cond_t *cond

puntatore all’oggetto condizione

Valore di ritorno

sempre 0

Sincronizzazione 58


oadcast

int pthread_cond_broadcast ( pthread_cond_t *cond )

Se esistono thread sospesi nella coda

associata a cond, vengono svegliati tutti

altrimenti nessun effetto

pthread_cond_t *cond

puntatore all’oggetto condizione

Valore di ritorno

sempre 0

Sincronizzazione 59


Valutazione condizione

Il thread svegliato deve rivalutare la condizione

l’altro thread potrebbe non aver testato la

condizione

la condizione potrebbe essere cambiata nel

frattempo

possono verificarsi wakeup “spuri”

pthread_mutex_lock(&mutex);

while(!condition_to_hold)

ptread_cond_wait(&cond, &mutex);

computation();

pthread_mutex_unlock(&mutex);

Sincronizzazione 60


Stato della coda

Non è prevista una funzione per

verificare lo stato della coda associata

a una condizione.

Sincronizzazione 61


Esempio di utilizzo (1 di 2)

Risorsa che può essere usata

contemporaneamente da MAX thread.

condition PIENO per la sospensione dei thread

M mutex associato a pieno

N_int numero di thread che stanno utilizzando la

risorsa

#define MAX 100

/*variabili globali*/

int N_in=0 /*numero thread che stanno utilizzando la risorsa*/

pthread_cond_t PIENO;

pthread_ mutex M;/*mutex associato alla cond. PIENO*/

Sincronizzazione 62


Esempio di utilizzo (2 di 2)

void codice_thread() {

/*fase di entrata*/

pthread_mutex_lock(&M);

/* controlla la condizione di ingresso*/

if(N_in == MAX)

pthread_cond_wait(&PIENO,&M);

/*aggiorna lo stato della risorsa */

N_in ++;

pthread_mutex_unlock(&M);


/*fase di uscita*/

pthread_mutex_lock(&M);

/*aggiorna lo stato della risorsa */

N_in --;

pthread_cond_signal(&PIENO);

pthread_mutex_unlock(&M);

}

Sincronizzazione 63


Esempio 8 (1 di 4)

#include

#include

#include

/* mutex */

pthread_mutex_t condition_mutex = PTHREAD_MUTEX_INITIALIZER;

/* condition variable*/

pthread_cond_t condition_cond = PTHREAD_COND_INITIALIZER;

Continua


Sincronizzazione 64


Esempio 8 (2 di 4)

void thread1_func(void *ptr) {

printf("Avvio dell’esecuzione del %s.\n",(char *)ptr);

sleep(2); /* pausa di 2 secondi */

printf("Thread 1 in procinto di entrare nella sezione critica.\n");

pthread_mutex_lock(&condition_mutex);

printf("Thread 1 nella sezione critica.\n");

printf("Thread 1 si sospende sulla condition variable.\n");

pthread_cond_wait(&condition_cond, &condition_mutex);

printf("Thread 1 riprende l’esecuzione.\n");

printf("Thread 1 in procinto di uscire dalla sezione critica.\n");

pthread_mutex_unlock(&condition_mutex);

printf("Thread 1 in procinto di terminare.\n");

}

Continua


Sincronizzazione 65


Esempio 8 (3 di 4)

void thread2_func(void *ptr) {

printf("Avvio dell’esecuzione del %s.\n",(char *)ptr);

sleep(5); /* pausa di 5 secondi */

printf("Thread 2 in procinto di entrare nella sezione critica.\n");

pthread_mutex_lock(&condition_mutex);

printf("Thread 2 nella sezione critica.\n");

printf("Thread 2 segnala l’evento della condition variable.\n");

pthread_cond_signal(&condition_cond);

printf("Thread 2 in procinto di uscire dalla sezione critica.\n");

pthread_mutex_unlock(&condition_mutex);

}

printf("Thread 2 in procinto di terminare.\n");

Continua


Sincronizzazione 66


Esempio 8 (4 di 4)

main() {

pthread_t thread1,thread2;

char *msg1="Thread 1";

char *msg2="Thread 2";

if(pthread_create(&thread1,NULL,(void *)&thread1_func,(void *)msg1)!=0){

perror("Errore nella creazione del primo thread.\n");

exit(1);

}

if(pthread_create(&thread2,NULL,(void *)&thread2_func,(void *)msg2)!=0){

perror("Errore nella creazione del secondo thread.\n");

exit(1);

}

pthread_join(thread1,NULL);

pthread_join(thread2,NULL);

exit(0);

}

Sincronizzazione 67


Esempio 9: incremento contatore

3)

(1 di

void *inc_count(void *idp) {

int j,i; double result=0.0; int *my_id = idp;

for (i=0; i


Esempio 9: incremento contatore

3)

(2 di

void *watch_count(void *idp) {

int *my_id = idp;

printf("Starting watch_count(): thread %d\n", *my_id);

pthread_mutex_lock(&count_mutex); /*Lock mutex and wait for signal. */

while (count


Esempio 9: incremento contatore

3)

(3 di

int main (int argc, char *argv[]) {

int i, res;

pthread_t threads[3];

pthread_attr_t attr;

/* Initialize mutex and condition variable objects */

pthread_mutex_init(&count_mutex, NULL);

pthread_cond_init (&count_threshold_cv, NULL);

}

/* Create threads and wait for all threads to complete */


/* Clean up and exit */

pthread_attr_destroy(&attr);

pthread_mutex_destroy(&count_mutex);

pthread_cond_destroy(&count_threshold_cv);

pthread_exit(NULL);

Sincronizzazione 70


Esempio 10 (1 di 6)

#include

#include

#include

#include

#include

#include

/* Numero di cicli di lettura/scrittura che vengono fatti dai thread */

#define CICLI 1

/* Lunghezza del buffer */

#define LUN 20

/* Numero di cicli di attesa a vuoto di uno scrittore */

#define DELAY_WRITER 200000

/* Numero di cicli di attesa a vuoto di uno scrittore */

#define DELAY_READER 2000000

Sincronizzazione 71


Esempio 10 (2 di 6)

/* Memoria Condivisa fra i thread ... */

struct {

/* Semaforo di mutua esclusione */

pthread_mutex_t mutex;

/* Variabile condition per il lettore */

pthread_cond_t lettore;

/* Variabile condition per gli scrittori */

pthread_cond_t scrittori;

/* Buffer */

char scritta[LUN+1];

/* Variabili per la gestione del buffer */

int primo, ultimo, elementi;

/* Numero di lettori e scrittori bloccati */

int blockscri, blocklet;

} shared = {PTHREAD_MUTEX_INITIALIZER,

PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER};

Sincronizzazione 72


Esempio 10 (3 di 6)

int main(void){

pthread_t s1TID, s2TID, lTID;

int res, i;

/* Inizializzo la stringa scritta */

for(i=0; i


Esempio 10 (4 di 6)

}

res = pthread_create(&lTID, NULL, lettore, NULL);

if (res != 0) printf("Errore nella creazione del primo thread\n");

res = pthread_create(&s1TID, NULL, scrittore1, NULL);

if (res != 0) {

printf("Errore nella creazione del secondo thread\n");

pthread_kill(s1TID, SIGKILL);exit(-1);

}

res = pthread_create(&s2TID, NULL, scrittore2, NULL);

if (res != 0) {

printf("Errore nella creazione del terzo thread\n");

pthread_kill(lTID, SIGKILL);pthread_kill(s1TID, SIGKILL);

return -1;

}

/* Aspetto che i tre thread finiscano ... */

pthread_join(s1TID, NULL);pthread_join(s2TID, NULL);

pthread_join(lTID, NULL);

printf("E' finito l'esperimento ....\n");

return (0);

Sincronizzazione 74


Esempio 10 (5 di 6)

void *scrittore1(void *in){

int i, j, k;

for (i=0; i


Esempio 10 (6 di 6)

void *lettore(void *in){

int i, k, j; char local[LUN+1]; local[LUN] = 0;

for (i=0; i


Esercizio 4

pthreads-4a-barrier.c

modificare in modo da ottenere la

sincronizzazione desiderata

tutti i thread si bloccano alla barriera

aspettando l'arrivo di tutti gli altri

tutti i thread proseguono l'esecuzione

quando l'ultimo ha raggiunto la barriera

suggerimento: usare una variabile

condition

pthreads-4b-barrier.c

soluzione dell'esercizio precedente

Sincronizzazione 77


pthreads-4a-barrier.c

2)

(1 di

#include

#include

#include

#include

#include

#define NUM_THREADS 4

/* Some variables are needed here... */

int n_threads; /* number of worker threads */

/* Complete the function body */

void barrier()

{

}

void *parallel_elaboration(void * arg)

{

int delay = rand()%6;

int i = *(int*)arg;

printf("[Thread %d] Waiting for %d secs...\n",i,delay);

sleep(delay);

printf("[Thread %d] ...elaboration finished, waiting for the other threads...\n",i);

barrier();

printf("[Thread %d] ...ok!\n",i);

pthread_exit(NULL);

}

Continua

Sincronizzazione 78


pthreads-4a-barrier.c

2)

(2 di

int main(void)

{

pthread_t tids[NUM_THREADS];

int params[NUM_THREADS];

int i, rc;

n_threads = NUM_THREADS;

/* Some initialization goes here... */

srand ( time(NULL) );

printf("[Main] Starting...\n");

for (i=0; i


Semafori classici


Semafori

I semafori sono primitive, implementate

attraverso dei contatori, fornite dal sistema

operativo per permettere la sincronizzazione

tra processi e/o thread.

Per queste primitive è garantita l'atomicità.

Quindi, ogni modifica o check del valore di

un semaforo può essere effettuata senza

sollevare race conditions.

Sincronizzazione 81


Race condition

Più processi accedono concorrentemente agli

stessi dati, e il risultato dipende dall'ordine di

interleaving dei processi.

Frequenti nei sistemi operativi multitasking, sia

per dati in user space sia per strutture in kernel.

Estremamente pericolose: portano al

malfunzionamento dei processi coo-peranti, o

anche (nel caso delle strutture in kernel space)

dell'intero sistema

difficili da individuare e riprodurre: dipendono da

informazioni astratte dai processi (decisioni dello

scheduler, carico del sistema, utilizzo della

memoria, numero di processori, . . . )

Sincronizzazione 82


Mutex vs Semaforo (1 di 2)

Il mutex è un tipo definito "ad hoc" per gestire la

mutua esclusione quindi il valore iniziale può

essergli assegnato anche in modo statico mediante

la macro PTHREAD_MUTEX_INITIALIZER.

Al contrario un semaforo come il sem_t deve essere

di volta in volta inizializzato dal programmatore col

valore desiderato.

Sincronizzazione 83


Mutex vs Semaforo (2 di 2)

Un semaforo può essere impiegato come un mutex

Differenza sostanziale: un mutex deve sempre

essere sbloccato dal thread che lo ha bloccato,

mentre per un semaforo l’operazione post può non

essere eseguita dal thread che ha eseguito la

chiamata wait.

inizializzo un mutex;

(1);

pthread_mutex_lock(&mutex);

sezione critica

pthread_mutex_unlock(&mutex);

inizializzo un semaforo

sem_wait(&sem);

sezione critica

sem_post(&sem);

Sincronizzazione 84


Semafori classici (generali) (1 di 2)

Semafori il cui valore può essere

impostato dal programmatore

utilizzati per casi più generali di

sincronizzazione

esempio: produttore consumatore

Interfaccia

operazione wait

operazione post (signal)

Sincronizzazione 85


Semafori classici (generali) (2 di 2)

Semafori classici e standard POSIX

non presenti nella prima versione dello

standard

introdotti insieme come estensione realtime

con lo standard IEEE POSIX 1003.1b

(1993)

Utilizzo

associati al tipo sem_t

includere l’header

#include

#include

Sincronizzazione 86


errno





Quasi tutte le funzioni delle librerie del C sono in grado di

individuare e riportare condizioni di errore, ed è una norma

fondamentale di buona programmazione controllare sempre

che le funzioni chiamate si siano concluse correttamente.

In genere le funzioni di libreria usano un valore speciale per

indicare che c'è stato un errore. Di solito questo valore è -1 o

un puntatore NULL o la costante EOF (a seconda della

funzione); ma questo valore segnala solo che c'è stato un

errore, non il tipo di errore.

Per riportare il tipo di errore il sistema usa la variabile globale

errno definita nell'header errno.h

Il valore di errno viene sempre impostato a zero all'avvio di un

programma.


La procedura da seguire è sempre quella di controllare errno

immediatamente dopo aver verificato il fallimento della

funzione attraverso il suo codice di ritorno.

Sincronizzazione 87


Stato di errore (1 di 2)

Per verificare la presenza di uno stato di errore si

usa la funzione ferror() che restituisce un valore

diverso da zero se questo stato esiste

effettivamente:

int ferror (FILE *flusso_di_file);

Per interpretare l'errore annotato nella variabile

errno e visualizzare direttamente un messaggio

attraverso lo standard error, si può usare la funzione

perror()

void perror (const char *s);

La funzione perror() mostra un messaggio in modo

autonomo, aggiungendo davanti la stringa che può

essere fornita come primo argomento

Sincronizzazione 88


Stato di errore (2 di 2)


In questo esempio si crea un errore, tentando di scrivere un

messaggio attraverso lo standard input. Se effettivamente si

rileva un errore associato a quel flusso di file, attraverso la

funzione ferror(), allora si passa alla sua interpretazione con

la funzione strerror()

#include

#include

#include

int main (void){

char *cp;

fprintf (stdin, “Hello world!\n");

if (ferror (stdin)){

cp = strerror (errno);

fprintf (stderr, "Attenzione: %s\n", cp);

}

return 0;

}

Eseguendo il programma si ottiene un messaggio di errore:

Attention: Bad file descriptor

Sincronizzazione 89


Esempio errno con i semafori



ret = sem_init(sem, pshared, value);

if (ret == -1){

printf("sem_init: thread %d,

%s: failed: %s\n",

pthread_self(),

msg, strerror(errno));

exit(1);

}

Sincronizzazione 90


Creazione semaforo

sem_t: tipo di dato associato al

semaforo

sem_t sem;

Sincronizzazione 91


Inizializzazione (1 di 2)

int sem_init( sem_t *sem, int pshared,

unsigned int value )

I semafori richiedono un’inizializzazione

esplicita da parte del programmatore

sem_init serve per inizializzare il valore

del contatore del semaforo specificato

come primo parametro

Sincronizzazione 92


Inizializzazione (2 di 2)





sem_t *sem

puntatore al semaforo da inizializzare, cioè l’indirizzo

dell’oggetto semaforo sul quale operare

int pshared

flag che specifica se il semaforo è condiviso fra più processi

se 1 il semaforo è condiviso tra processi

se 0 il semaforo è privato del processo

attualmente l'implementazione supporta solamente

pshared = 0

unsigned int *value

valore iniziale da assegnare al semaforo

Valore di ritorno

0 in caso di successo,

-1 altrimenti con la variabile errno settata a EINVAL se il

semaforo supera il valore SEM_VALUE_MAX

Sincronizzazione 93


Interfaccia wait (1 di 2)

Consideriamo il semaforo come un intero, sul cui

valore la funzione wait esegue un test

Se il valore del semaforo è minore o uguale a zero

(semaforo rosso), la wait si blocca, forzando un

cambio di contesto a favore di un altro dei processi

pronti che vivono nel sistema

Se il test ha successo cioè se il semaforo presenta

un valore maggiore od uguale ad 1 (semaforo

verde), la wait decrementa tale valore e ritorna al

chiamante, che può quindi procedere nella sua

elaborazione.

void wait (semaforo s) {

s.count--;

if (s.count < 0)

;

}

Sincronizzazione 94


Interfaccia wait (2 di 2)

Due varianti

wait: bloccante (standard)

trywait: non bloccante (utile per evitare

deadlock)

Sincronizzazione 95


wait

int sem_wait( sem_t *sem )

sem_t *sem

puntatore al semaforo da decrementare

Valore di ritorno

sempre 0

Sincronizzazione 96


trywait

int sem_trywait( sem_t *sem )

sem_t *sem

puntatore al semaforo da decrementare

Valore di ritorno

0 in caso di successo

-1 se il semaforo ha valore 0

setta la variabile errno a EAGAIN

Sincronizzazione 97


Interfaccia signal

L'operazione di signal incrementa il

contatore del semaforo

Se a seguito di tale azione il contatore

risultasse ancora minore od uguale a zero,

significherebbe che altri processi hanno

iniziato la wait ma hanno trovato il semaforo

rosso

la signal sveglia quindi uno di questi;

pertanto esiste una coda di processi

bloccati per ciascun semaforo.

void signal (semaforo s) {

s.count++;

if (s.count


sem_post

int sem_post( sem_t *sem )



sem_t *sem

puntatore al semaforo da incrementare

Valore di ritorno

0 in caso di successo

-1 altrimenti con la variabile errno settata in

base al tipo di errore

sem_post restituisce EINVAL se il semaforo supera il

valore SEM_VALUE_MAX dopo l’incremento

Sincronizzazione 99


sem_destroy

int sem_destroy( sem_t *sem )



sem_t *sem

puntatore al semaforo da distruggere

Valore di ritorno

0 in caso di successo

-1 altrimenti con la variabile errno settata in

base al tipo di errore

sem_destroy restituisce EBUSY se almeno un thread è

bloccato sul semaforo

Sincronizzazione 100


sem_getvalue

Serve per poter leggere il valore attuale del

contatore del semaforo

int sem_getvalue( sem_t *sem, int *sval )




sem_t *sem

puntatore del semaforo di cui leggere il valore

int *sval

valore del semaforo

Valore di ritorno

sempre 0

Sincronizzazione 101


Esempio 7: lettori e scrittori (1 di

5)

#include

#include

#include

#define LUN 20

#define CICLI 1

#define DELAY 100000

struct {

char scritta[LUN+1];

/* Variabili per la gestione del buffer */

int primo, ultimo;

/* Variabili semaforiche */

sem_t mutex, piene, vuote;

} shared;

void *scrittore1(void *);

void *scrittore2(void *);

void *lettore(void *);

Continua


Sincronizzazione 102


Esempio 7: lettori e scrittori (2 di

5)

int main(void) {

pthread_t s1TID, s2TID, lTID;

int res, i;

shared.primo = shared.ultimo = 0;

sem_init(&shared.mutex, 0, 1);

sem_init(&shared.piene, 0, 0);

sem_init(&shared.vuote, 0, LUN);

pthread_create(&lTID, NULL, lettore, NULL);

pthread_create(&s1TID, NULL, scrittore1, NULL);

pthread_create(&s2TID, NULL, scrittore2, NULL);

pthread_join(s1TID, NULL);

pthread_join(s2TID, NULL);

pthread_join(lTID, NULL);

printf("E' finito l'esperimento ....\n");

}

Continua


Sincronizzazione 103


Esempio 7: lettori e scrittori (3 di

5)

void *scrittore1(void *in) {

int i, j, k;

for (i=0; i


Esempio 7: lettori e scrittori (4 di

5)

void *scrittore2(void *in) {

int i, j, k;

for (i=0; i


Esempio 7: lettori e scrittori (5 di

5)

void *lettore(void *in) {

int i, j, k; char local[LUN+1]; local[LUN] = 0; /* Buffer locale */

for (i=0; i


Esercizi


Esercizio 1

pthreads-1a-simple.c

analizzare l'output

cambiare pthreads_exit(NULL) in

return(0)

cosa succede?

aggiungere il passaggio di un parametro ai

thread passando a tutti lo stesso valore

pthreads-1b-simple.c

cosa cambia rispetto al precedente?

pthreads-1c-simple.c

soluzione dell'esercizio precedente

Sincronizzazione 108


pthreads-1a-simple.c

#include

#include

#include

#include

#define NUM_THREADS 3

void *thread_function(void* arg){

printf("[Thread] Waiting for termination...\n");

sleep(5);

printf("[Thread] ...thread finished!\n");

pthread_exit(NULL);

}

int main(void){

pthread_t tids[NUM_THREADS];

int i, rc;

printf("[Main] Starting...\n");

for (i=0; i


pthreads-1b-simple.c (1 di

2)

#include

#include

#include

#include

#define NUM_THREADS 3

void *thread_function(void* arg){

printf("[Thread] Waiting for termination...\n");

sleep(5);

printf("[Thread] ...thread finished!\n");

pthread_exit(NULL);

}

Continua

Sincronizzazione 110


pthreads-1b-simple.c (2 di

2)

int main(void){

pthread_t tids[NUM_THREADS];

int i, rc;

printf("[Main] Starting...\n");

for (i=0; i


Esercizio 2

pthreads-2a-args.c

analizzare l'output

modificare in modo da ottenere un

funzionamento corretto

pthreads-2b-args.c

soluzione dell'esercizio precedente

Sincronizzazione 112


pthreads-2a-args.c

#include

#include

#include

#include

#define NUM_THREADS 3

void *thread_function(void* arg)

{

int i = *(int*)arg;

printf("[Thread %d] Waiting for termination...\n",i);

sleep(5);

printf("[Thread %d] ...thread finished!\n",i);

pthread_exit(NULL);

}

int main(void)

{

pthread_t tids[NUM_THREADS];

int i, rc;

printf("[Main] Starting...\n");

for (i=0; i

More magazines by this user
Similar magazines