istre-07-pthread.ppt [modalità...
TRANSCRIPT
14/05/2014
1
7. Pthread library
1. Descrizione generale
2. Gestione di thread
3. Scheduler in Linux
4. Gestione del tempo
5. Gestione di thread periodici
6. Mutua esclusione
7. Esempi
Sommario
Un processo rappresenta l'unità di esecuzione in un SO.
Programma: entità passiva descritta dal codice sorgente.
Processo: entità attiva determinata dall'esecuzione delprogramma su un particolare insieme di dati.
Processo
Un processo è rappresentato dai seguenti elementi:
codice: istruzioni compilate ed eseguibili;
dati: spazio di memoria per le variabili globali;
contesto: stato dei registri di CPU;
stack: spazio di memoria per le variabili locali.
parametri: tipo, priorità, argomenti, ...
I processi non condividono memoria!
Processi diversi
eseguono codici diversi;
accedono a spazi di memoria distinti.
Essi possono comunicare mediante un meccanismo ascambio di messaggi messo a disposizione dal SO:
Processo
datidati
P1 P2channel
Un processo può essere costituito da più sottoprocessiconcorrenti, detti thread.
I thread di uno stesso processo
condividono lo stesso spazio di memoria;
hanno stack distinti;
possono eseguire lo stesso codice.
Processi e thread
dati
Pi1 2 3
Esempio di processocomposto da 3 thread:
Funzioni
pthread_create: crea un thread
pthread_exit: termina il thread chiamante
pthread_cancel: termina un altro thread
pthread_join: aspetta la terminazione di un thread
Compilazione
Per utilizzare la libreria, inserire #include <pthread.h>
gcc prova.c -o prova -lpthread -lrt
14/05/2014
2
Thread creation
int pthread_create(thread_t *id,pthread_attr_t *attr,void *(*body)(void *),void *arg);
Crea un thread:
id memorizza l'identificatore unico del thread creato.
attr puntatore a una struttura che definisce gli attributi delthread (se NULL utilizza valori di default).
body puntatore alla funzione che definisce il codice del thread.
arg puntatore al singolo argomento del thread. Per passare piùargomenti definire un puntatore a struttura.
Thread creation
Esempio:
pthread_t tid;
pthread_create(&tid, NULL, task, NULL);
void *task(){
printf("I am a simple thread.\n");}
usa attributidi default
nessunargomento
Thread ID
Thread ID
Poiché l'identificatore di thread viene restituito a chieffettua la create, esistono le seguenti funzioni:
pthread_self()
restituisce l'ID del thread chiamante
pthread_equal(tid1, tid2)
restituisce un valore 0, se tid1 = tid2, 0 altrimenti.
pthread_t tid1, tid2;
tid1 = pthread_self();
if (pthread_equal(tid1,tid2))return 0;
else return 1;
Thread creation
Main thread thread th1
pthread_create(&th1,…
pthread_t th1;
Dopo la create, non è detto che il thread sia attivo, esso hasolo un ID e le strutture dati necessarie. Può accadere che: il thread non sia ancora partito; sia attivo in coda pronti; sia già terminato.
Un thread può terminare per diverse cause:
• dopo che viene eseguita l'ultima istruzione dellafunzione ad esso associata (terminazione normale);
• quando esso chiama la primitiva pthread_exit.
• quando viene terminato da un altro thread attraverso laprimitiva pthread_cancel.
• quando termina il processo a cui appartiene, nel nostrocaso il main(), a causa di una terminazione normale o diuna chiamata alla primitiva exit.
Thread termination Thread termination
void pthread_exit(void *retval);
Termina il thread chiamante e restituisce in retval il valorerestituito dal thread terminato.
int pthread_cancel(pthread_t th);
Invia una richiesta di cancellazione per il thread th. Laterminazione effettiva dipende da due attributi del thread:
state: enabled (default) o disabled
type: asynchronous o deferred (default)
assegnabili per mezzo delle primitive
pthread_setcancelstate e pthread_setcanceltype
14/05/2014
3
Thread joining
int pthread_join(pthread_t th, void **retval);
Aspetta la terminazione del thread indicato da th.
• Se retval NULL, il valore di ritorno del threadterminato viene copiato in *retval.
• Se il thread è già terminato, *retval assume il valorePTHREAD_CANCELED.
• Retistuisce 0 in caso di successo, o un codice di errore.
E' possibile attendere la terminazione di un threadattraverso la funzione pthread_join:
Esempio - create
#include <pthread.h>
void *task(void *p);
int main(){pthread_t tid1, tid2;int tret1, tret2;int a = 1, b = 2;
tret1 = pthread_create(&tid1, NULL, task, (void*)&a);tret2 = pthread_create(&tid2, NULL, task, (void*)&b);
pthread_join(tid1, NULL);pthread_join(tid2, NULL);
printf("Thread1 returns %d\n", tret1);printf("Thread2 returns %d\n", tret2);return 0;
}
vengono creati 2 thread con lostesso codice, che si differenzianomediante il parametro passato.
void *task(void *p){int *pi;
pi = (int *)p;printf("This is TASK %d\n", *pi);
}
Esempio - create Joinable vs. detached
Quando si chiama la pthread_join è possibile che il threadsia già terminato, quindi il sistema deve mantenere delleinformazioni anche dopo la terminazione.
Tali informazioni vengono distrutte (e la memorialiberata) con l'operazione di join.
Se non ci si deve sincronizzare con un thread e non sichiama la pthread_join, la memoria non viene liberata.
Per evitare spreco di memoria quando la join non èrichiesta, un thread può essere dichiarato di tipodetached (per default, un thread è di tipo joinable).
Joinable vs. detached
Esistono due modi per definire un thread di tipo detached:
Usare l'attributo detachstate in fase di creazione.
Chiamare la funzione pthread_detach().
Quando termina un thread di tipo detached, il sistemalibera automaticamente tutte le strutture dati di sistema daesso utilizzate.
Detached threads
int pthread_detach(pthread_t th);
Definisce il thread th come detached. Retistuisce 0 in casodi successo, o un codice di errore.
Tale funzione può essere chiamata anche dallo stessothread che si vuole definire come detached.
In tal caso, l'identificatore del thread può essere recuperatochiamando la funzione pthread_self():
pthread_t my_id;
my_id = pthread_self();
pthread_detach(my_id);
14/05/2014
4
Thread attributes
Specificano le caratteristiche del thread:
stack size dimensione della memoria di stack.
state tipologia (joinable o detached).
priority livello di priorità del thread.
scheduler algoritmo con cui schedulare il thread.
Gli attributi devono essere inizializzati e distrutti:
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
Lo stato del thread che deve essere creato viene definitomodificando gli attributi di default per mezzo dellafunzione pthread_attr_setdetachstate():
pthread_t tid;pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,
PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, task, NULL);
Thread state
Thread priority
struct sched_param mypar;
mypar.sched_priority = 23;
pthread_attr_setschedparam(&myatt, &mypar);
struct sched_param {
int sched_priority;};
La priorità di un thread viene specificata mediante unastruttura contenente un solo campo:
La priorità deve essere prima assegnata per mezzo di unastruttura locale e poi inserita negli attributi del threadmediante un'apposita funzione:
Linux Scheduling
CPU
priority
Linux prevede 99 livelli di priorità: 1 (bassa), 99 (alta).Tuttavia lo standard POSIX richiede di garantirne 32.
Ad ogni priorità è associata una coda, in cui vengonoinseriti i thread con la stessa priorità.
Il primo thread della coda a più alta priorità vieneselezionato come running task:
Linux prevede tre tipologie di scheduler (per ogni thread):
SCHED_OTHER (Default Policy)Scheduler Round Robin con meccanismo di aging.
SCHED_FIFOScheduler prioritario: thread a pari priorità vengonogestiti con politica FIFO.
SCHED_RRScheduler prioritario: thread a pari priorità vengonogestiti con politica Round‐Robin.
Linux Scheduling
SCHED_FIFO e SCHED_RR sono dette politiche real timee possono essere usate solo da root.
Politiche real time
SCHED_FIFO (Fixed‐Priority Scheduling + FIFO)
Scheduler prioritario: thread a pari priorità vengonogestiti con politica FIFO. Un thread viene eseguito fino aterminazione, cancellazione, bloccaggio, o preemption.
SCHED_RR (Fixed‐Priority Scheduling + RR)
Scheduler prioritario: thread a pari priorità vengonogestiti con politica Round‐Robin. Un thread vieneeseguito fino a terminazione, cancellazione, bloccaggio,preemption o esaurimento del quanto temporale.
Il quanto dipende dal sistema e non può essere definitodall'utente, ma può essere conosciuto chiamando lafunzione sched_rr_get_interval().
14/05/2014
5
Round-Robin quantum
int sched_rr_get_interval(pid_t pid, struct timespec *tp);
Scrive nella struttura puntata da tp il valore del quantotemporale utilizzato dallo scheduler Round‐Robin per ilprocesso identificato da pid.
Se pid = 0, viene restituito il valore del quanto utilizzato peril processo chiamante:
#include <sched.h>
struct timespec q;
sched_rr_get_interval(0, &q);
printf("Q: %ld s, %ld ns\n",q.tv_sec, q.tv_nsec);
Anche lo scheduler viene definito attraverso gli attributi,utilizzando la funzione pthread_attr_setschedpolicy.
pthread_attr_t myatt;
pthread_attr_init(&myatt);
pthread_attr_setinheritsched(&myatt, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&myatt, SCHED_FIFO);
Linux Scheduler
Per default, un thread viene schedulato con la stessapolitica usata per il thread padre, ossia SCHED_OTHER.
Per usare politiche diverse è necessario comunicare alkernel tale intenzione attraverso la funzionepthread_attr_setinheritsched:
Esempio - create
int main(){pthread_attr_t myatt; /* attribute structure */struct sched_param mypar; /* priority structure */pthread_t tid; /* thread id */
pthread_attr_init(&myatt);
pthread_attr_setinheritsched(&myatt, PTHREAD_EXPLICIT_SCHED);pthread_attr_setschedpolicy(&myatt, SCHED_FIFO);
mypar.sched_priority = 23;pthread_attr_setschedparam(&myatt, &mypar);
err = pthread_create(&tid, &myatt, task, NULL);
pthread_join(tid, NULL);pthread_attr_destroy(&myatt);
}
Per evitare che applicazioni errate blocchino il sistema,esiste un meccanismo di protezione temporale chegarantisce al sistema una minima quantità di tempo.
Tale meccanismo è configurabile attraverso 2 parametri:
/proc/sys/kernel/sched_rt_period_us
stabilisce il periodo (in microsecondi) della reservation.Default = 1000000 µs (1 secondo).
/proc/sys/kernel/sched_rt_runtime_us
stabilisce il budget (in microsecondi) da allocare alleattività real‐time in ogni periodo. Default = 950000 µs.
Protezione temporale
Dunque, per default, il 95% della CPU è riservato alleattività real time e il 5% a quelle gestite con SCHED_OTHER.
Ciò significa che un thread ad alta priorità può essereinterrotto anche se schedulato con SCHED_FIFO.
La percentuale riservata ai thread real time può esseremodificata o disabilitata (solo da root):
Protezione temporale
> echo 980000 > /proc/sys/kernel/sched_rt_runtime_us
Imposta il budget al 98%.
> echo ‐1 > /proc/sys/kernel/sched_rt_runtime_us
Imposta il budget al 100%.
14/05/2014
6
Tipi di clock
Linux supporta i seguenti clock:
CLOCK_REALTIME: è il valore piùvicino possibile al tempo assoluto.Tuttavia, esso può essere discontinuoin quanto può subire aggiustamenti.
CLOCK_MONOTONIC: rappresenta iltempo trascorso a partire da unimprecisato istante. Esso non èaffetto da aggiustamenti, per cui è lasoluzione migliore per misurare iltempo intercorso tra due eventi.
Rappresentazione del tempo
Nello standard POSIX, il tempo è rappresentato per mezzodella seguente struttura, definita in <time.h>:
struct timespec {time_t tv_sec; /* seconds */long tv_nsec; /* nanoseconds */
}
Il tipo time_t dipende dall'implementazione, ma disolito è un intero a 32 bit.
Purtroppo la libreria standard non fornisce funzioni disupporto per compiere operazioni sul tempo, per cui ènecessario definirsi delle funzioni ausiliarie.
Copia di tempi
void time_copy(struct timespec *td,struct timespec ts)
{td->tv_sec = ts.tv_sec;td->tv_nsec = ts.tv_nsec;
}
Tale funzione copia il tempo sorgente ts nella variabiledestinataria puntata da td:
Somma di millisecondi
void time_add_ms(struct timespec *t, int ms){
t->tv_sec += ms/1000;t->tv_nsec += (ms%1000)*1000000;
if (t->tv_nsec > 1000000000) {t->tv_nsec -= 1000000000;t->tv_sec += 1;
}}
Tale funzione somma al tempo t un valore ms espresso inmillisecondi:
Confronto di tempi
int time_cmp(struct timespec t1,struct timespec t2)
{if (t1.tv_sec > t2.tv_sec) return 1;if (t1.tv_sec < t2.tv_sec) return -1;if (t1.tv_nsec > t2.tv_nsec) return 1;if (t1.tv_nsec < t2.tv_nsec) return -1;return 0;
}
Tale funzione confronta due tempi t1 e t2 e restituisce0 se uguali, 1 se t1 > t2, ‐1 se t1 < t2:
Funzioni disponibili
int clock_getres(clockid_t clk_id, struct timespec *res);
Se res NULL, scrive in res la risoluzione del clockspecificato in clk_id. Questa dipende dalla particolareimplementazione e non può essere impostata.
int clock_gettime(clockid_t clk_id, struct timespec *t);
Memorizza in t il valore del clock specificato in clk_id.
int clock_settime(clockid_t clk_id, struct timespec *t);
Imposta il clock specificato in clk_id al valore t. Se t non èmultiplo della risoluzione, esso viene troncato.
14/05/2014
7
Funzioni disponibili
int clock_nanosleep(clockid_t clk_id, int flag,const struct timespec *t, struct timespec *rem);
Sospende l'esecuzione del thread chiamante finché il clockclk_id non raggiunge il tempo specificato in t.
Se flag = 0, il tempo t viene interpretato come relativo altempo corrente;
Se flag = TIMER_ABSTIME, il tempo t viene interpretatocome assoluto.
Se il thread viene risvegliato prima del tempo impostato,il tempo rimanente viene memorizzato in rem.
Esempio
struct timespec t; /* absolute time */struct timespec dt; /* time interval */int delta = 500; /* delay in milliseconds */
clock_gettime(CLOCK_MONOTONIC, &t);time_add_ms(&t, delta);
/* suspend until absolute time t */
clock_nanosleep(CLOCK_MONOTONIC,TIMER_ABSTIME, &t, NULL);
/* suspend for a relative interval of 500 ms */
dt.tv_sec = 0;dt.tv_nsec = delta*1000000;clock_nanosleep(CLOCK_MONOTONIC, 0, &dt, NULL);
Un thread non periodico
void *task(void *arg){struct timespec t;int period = 100; /* period in milliseconds */
while (1) {
/* do useful work */
clock_gettime(CLOCK_MONOTONIC, &t);
time_add_ms(&t, period);
clock_nanosleep(CLOCK_MONOTONIC,TIMER_ABSTIME, &t, NULL);
}}
Il task ha un periodo variabile pari a 100 ms + il tempo dirisposta (che varia da job a job).
Un thread periodico
void *task(void *arg){struct timespec t;int period = 100; /* period in milliseconds */
clock_gettime(CLOCK_MONOTONIC, &t);time_add_ms(&t, period);
while (1) {
/* do useful work */
clock_nanosleep(CLOCK_MONOTONIC,TIMER_ABSTIME, &t, NULL);
time_add_ms(&t, period);}
}
Controllo deadline miss
void *task(void *arg){struct timespec t, now;int period = 100; /* period in milliseconds */
clock_gettime(CLOCK_MONOTONIC, &t);time_add_ms(&t, period);
while (1) {
/* do useful work */
clock_gettime(CLOCK_MONOTONIC, &now);if (time_cmp(now, t) > 0) exit(-1);
clock_nanosleep(CLOCK_MONOTONIC,TIMER_ABSTIME, &t, NULL);
time_add_ms(&t, period);}
}
14/05/2014
8
Thread periodici
Un thread periodico, dunque, segue il seguente schema:
void *task(void *p){<local state variables>
set_period();
while (1) {
<thread body>
if (deadline_miss()) <do action>;wait_for_period();
}}
Struttura di parametri
Conviene definire una struttura per i parametri del thread:
struct task_par {int arg; /* task argument */long wcet; /* in microseconds */int period; /* in milliseconds */int deadline; /* relative (ms) */int priority; /* in [0,99] */int dmiss; /* no. of misses */struct timespec at; /* next activ. time */struct timespec dl; /* abs. deadline */
};
Tale struttura deve essere inizializzata da chi effettuala thread_create e passata come argomento al thread.
Occorre definire una stuttura per ogni thread.
Esempio: mainstruct sched_param mypar;struct task_partp[NT];pthread_attr_t att[NT];pthread_t tid[NT];
for (i=0; i<NT; i++) {tp[i].arg = i;tp[i].period = 100;tp[i].deadline = 80;tp[i].priority = 20;tp[i].dmiss = 0;
pthread_attr_init(&att[i]);pthread_attr_setinheritsched(&att[i], PTHREAD_EXPLICIT_SCHED);pthread_attr_setschedpolicy(&att[i], SCHED_FIFO);mypar.sched_priority = tp[i].priority;pthread_attr_setschedparam(&att[i], &mypar);pthread_create(&tid[i], &att[i], task, &tp[i]);
}
Se noto, il WCET può essereutilizzato per il test di garanzia.
La deadline assoluta vieneimpostata online appena notol'istante di attivazione.
array di variabiliper tutti i thread
Esempio: threadvoid *task(void *arg){<local state variables>struct task_par *tp;
tp = (struct task_par *)arg;i = tp->arg;
set_period(tp);
while (1) {
<thread body>
if (deadline_miss(tp))printf("!");
wait_for_period(tp);}
}
recupera il puntatore atask_par
recuperal'argomento
set_period()
void set_period(struct task_par *tp){struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);time_copy(&(tp->at), t);time_copy(&(tp->dl), t);time_add_ms(&(tp->at), tp->period);time_add_ms(&(tp->dl), tp->deadline);
}
Legge il tempo corrente e calcola il successivo istante diattivazione e la deadline assoluta del task.
NOTA: il timer non viene impostato per interrompere.
wait_for_period()
void wait_for_period(struct task_par *tp){
clock_nanosleep(CLOCK_MONOTONIC,TIMER_ABSTIME, &(tp->at), NULL);
time_add_ms(&(tp->at), tp->period);time_add_ms(&(tp->dl), tp->period);
}
Sospende il thread chiamante fino all'attivazione successivae, al risveglio, ricalcola l'istante di attivazione e la deadline.
NOTA: anche se il thread esegue la time_add_ms() in unistante maggiore di quello di risveglio, il calcolorisulta corretto.
14/05/2014
9
deadline_miss()
int deadline_miss(struct task_par *tp){struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (time_cmp(now, tp->dl) > 0) {tp->dmiss++;return 1;
}return 0;
}
Se il thread è in esecuzione al tempo di riattivazione,incrementa il campo dmiss e restituisce 1, altrimentirestituisce 0.
Sincronizzazione fra thread
In generale, esistono i seguenti meccanismi persincronizzare più thread:
Join consente di aspettare la terminazione di uno opiù thread.
Semaphore consente mutua esclusione e sincronizzazione.
Mutex consente di eseguire codice in mutuaesclusione
Condition consente di riattivarsi quando una variabilecondivisa raggiunge un valore prefissato.
Barrier consente a più thread di sospendersi fino ache tutti abbiano eseguito una certaoperazione.
Thread Joining
pthread_create()
pthread_join()
Main thread
thread 1
pthread_exit()
thread 2
pthread_exit()
pthread_create()
pthread_join()
consente di aspettare la terminazione di uno o più thread
Semaphores
sem_init: inizializza un semaforo.
sem_destroy: libera la memoria di un semaforo nonpiù utilizzato.
sem_wait: wait su semaforo.
sem_post: signal su semaforo.
sem_getvalue: restituisce il valore del semaforo.
I semafori generali non sono parte della libreria pthread,ma sono definiti nello standard POSIX:
Per utilizzarli occorre includere il file <semaphore.h>.
Un semaforo è una variabile di tipo sem_t
Funzioni principali:
Inizializzazione semafori
int sem_init (sem_t *sem, int pshared, unsigned int v);
Inizializza un semaforo:
sem indirizzo del semaforo;
pshared se 0, indica che il semaforo è condiviso frathread (dunque deve essere dichiarato globale);se 0, indica che è condiviso tra processi.
v valore iniziale del semaforo;
Restituisce 0 in caso di successo, ‐1 in caso di errore.
14/05/2014
10
Uso semafori
int sem_wait (sem_t *sem);
Se il valore corrente del semaforo è 0, blocca il threadchiamante finché non viene eseguita una sem_post,altrimenti decrementa il semaforo ed esce.
Entrambe le funzioni restituiscono 0 in caso di successo, ‐1in caso di errore.
int sem_post (sem_t *sem);
Se esistono thread bloccati sul semaforo, risveglia quello apiù alta priorità, altrimenti incrementa il semaforo ed esce.
Uso semafori
int sem_destroy (sem_t *sem);
Dealloca la memoria utilizzata per il semaforo.
Entrambe le funzioni restituiscono 0 in caso di successo, ‐1in caso di errore.
int sem_getvalue (sem_t *sem, int *pval);
Legge il valore del semaforo e lo scrive nella variabilepuntata da pval.
Esempio semafori
#include <semaphore.h>
sem_t sem1, sem2, sem3; /* definisce 3 semafori */
int main(){
/* inizializza sem1 per la sincronizzazione */sem_init(&sem1, 0, 0);
/* inizializza sem2 per la mutua esclusione */sem_init(&sem2, 0, 1);
/* inizializza sem3 per l'accesso simultaneo *//* di max 4 thread ad una risorsa con 4 unità */sem_init(&sem3, 0, 4);
Sincronizzazione su evento
thread 1 thread 2
sem_post(&event)
sem_wait(&event)
sem_t event;
sem_init(&event, 0, 0);
Valore iniziale = 0
struct point {int x;int y;
} p;
void *writer();void *reader();sem_t s;
int main(){pthread_t tid1, tid2;
sem_init(&s, 0, 1);
pthread_create(&tid1, NULL, writer, NULL);pthread_create(&tid2, NULL, reader, NULL);
/* do some useful work */}
Struttura globale condivisa daithread in mutua esclusione.
Mutua esclusione
Definizione del semaforo
Inizializzazione del semaforo
void *writer(){
sem_wait(&s);p.x++;p.y++;sem_post(&s);
}
Mutua esclusione
void *reader(){
sem_wait(&s);printf("(%d,%d)\n", p.x, p.y);sem_post(&s);
}
14/05/2014
11
Semafori Mutex
pthread_mutex_init: inizializza un semaforo.
pthread_mutex_destroy: libera la memoria di un mutexche non serve più.
pthread_mutex_lock: wait su semaforo.
pthread_mutex_unlock: signal su semaforo.
Un MUTEX è un tipo particolare di semaforo binario conalcune restrizioni (mirate a ridurre gli errori):
può solo essere usato per la mutua esclusione, nonper la sincronizzazione;
un mutex occupato da un thread può essere sbloccatosolo dallo stesso thread.
Funzioni principali:
Inizializzazione Mutex
Prima di essere utilizzato, un mutex dev'essere dichiarato einizializzato. Esistono 3 modi per inizializzare un mutex:
pthread_mutex_t mux; /* define a mutex */pthread_mutexattr_t matt; /* define attributes */
/* modo 1: inizializzazione diretta */pthread_mutex_t mux = PTHREAD_MUTEX_INITIALIZER;
/* modo 2: inizializzazione con valori di default */pthread_mutex_init(&mux, NULL);
/* modo 3: inizializzazione con attributi */pthread_mutexattr_init(&matt);pthread_mutex_init(&mux, &matt);
Nell'esempio riportato, i tre modi sono equivalenti einizializzano il mutex con i valori di default.
Esempio - Mutexstruct point {
int x;int y;
} p;
void *writer();void *reader();pthread_mutex_t mux = PTHREAD_MUTEX_INITIALIZER;
int main(){pthread_t tid1, tid2;
pthread_create(&tid1, NULL, writer, NULL);pthread_create(&tid2, NULL, reader, NULL);
pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}
Struttura globale condivisa daithread in mutua esclusione.
Definizione e inizializzazionedel semaforo.
void *writer(){
pthread_mutex_lock(&mux);p.x++;p.y++;pthread_mutex_unlock(&mux);
}
Esempio - Mutex
void *reader(){
pthread_mutex_lock(&mux);printf("(%d,%d)\n", p.x, p.y);pthread_mutex_unlock(&mux);
}
Mutex protocols
Linux prevede tre tipologie di protocolli sui mutex:
PTHREAD_PRIO_NONE
Nessun protocollo (semafori classici).
PTHREAD_PRIO_INHERIT
Protocollo di Priority Inheritance.
PTHREAD_PRIO_PROTECT
Protocollo di Immediate Priority Ceiling(anche noto come Highest Locker Priority).
NOTA: I semafori POSIX non prevedono tali protocolli.
Priority Inheritance
#define _GNU_SOURCE
pthread_mutex_t mux1, mux2; /* define 2 mutexes */pthread_mutexattr_t matt; /* define mutex attrib. */
pthread_mutexattr_init(&matt); /* initialize attributes */pthread_mutexattr_setprotocol(&matt, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&mux1, &matt);pthread_mutex_init(&mux2, &matt);
pthread_mutexattr_destroy(&matt); /* destroy attributes */
Occorre inserire in testa al file: #define _GNU_SOURCE
Ogni semaforo può usare un protocollo diverso.
La stessa variabile matt può essere utilizzata perinizializzare semafori diversi.
14/05/2014
12
Immediate Priority Ceiling
#define _GNU_SOURCE
pthread_mutex_t mux1, mux2; /* define 2 mutexes */pthread_mutexattr_t matt; /* define mutex attrib. */
pthread_mutexattr_init(&matt);pthread_mutexattr_setprotocol(&matt, PTHREAD_PRIO_PROTECT);
pthread_mutexattr_setprioceiling(&matt, 10);pthread_mutex_init(&mux1, &matt);
pthread_mutexattr_setprioceiling(&matt, 15);pthread_mutex_init(&mux2, &matt);
pthread_mutexattr_destroy(&matt);
Il ceiling del semaforo deve essere uguale alla massimapriorità dei task che lo usano.
Condition variables
Tale meccanismo consente ad un thread di attendere unvalore prefissato di una variabile condivisa:
x != 8 sleepY
N
x = x + 1
x == 8
N
Y
Il loop è necessario in quanto il risveglio può
essere dovuto ad altre cause
Waiting thread Signaling thread
Se più thread sono in attesa, è possibile svegliarne solo uno oppure tutti insieme
Condition variables
lock(mux);while (x != 8) {
sleep(cond_var, mux);}unlock(mux);
lock(mux);x = x + 1;if (x == 8) {
unlock(mux);signal(cond_var);
}else unlock(mux);
Sono sempre usate insieme a un mutex:
Prima di bloccarsi, il mutex viene rilasciatoin modo atomico e riacquisito al risveglio.
Waiting thread Signaling thread
pthread_mutex_t mux; /* define a mutex */pthread_cond_t cv; /* define a cond. variable */int count = 0; /* variable to be checked */
int main(){
/* initialize variables with default attributes */pthread_mutex_init(&mux, NULL);pthread_cond_init(&cv, NULL);
/* do some useful work */
/* reclaim memory */pthread_mutex_destroy(&mux);pthread_cond_destroy(&cv);
}
Condition variables
void *watch_count(){
/* wait until count reaches the THRESHOLD */
pthread_mutex_lock(&mux);
while (count < THRESHOLD) {
pthread_cond_wait(&cv, &mux);}
pthread_mutex_unlock(&mux);
/* do some useful work */
pthread_exit(NULL);
}
Waiting thread
void *inc_count(){
/* do some useful work */
pthread_mutex_lock(&mux);
count++;
if (count == THRESHOLD) {
pthread_mutex_unlock(&mux);pthread_cond_signal(&cv);
}else pthread_mutex_unlock(&mux);
/* do some useful work */
pthread_exit(NULL);
}
Signaling thread
14/05/2014
13
void *inc_count(){
/* do some useful work */
pthread_mutex_lock(&mux);
count++;
if (count == THRESHOLD) {
pthread_mutex_unlock(&mux);pthread_cond_broadcast(&cv);
}else pthread_mutex_unlock(&mux);
/* do some useful work */
pthread_exit(NULL);
}
Brodcasting
Si possono risvegliare tutti i thread in attesa, mediante
una broadcast
Note
int pthread_cond_signal (pthread_cond_t *cond);int pthread_cond_broadcast (pthread_cond_t *cond);
La signal risveglia uno dei thread bloccati sulla variabilecondition. Il thread svegliato dipende dallo scheduler(per scheduler real time il thread svegliato è quello apriorità più alta).
La broadcast risveglia tutti i thread bloccati sullavariabile condition.
Entrambe le funzioni non hanno effetto se non ci sonothread bloccati.
Barrier
thread 1 thread 2 thread 3
barrier_wait()
barrier_wait()
barrier_wait()
B A R R I E R
Ogni thread si blocca finché tutti abbiano eseguito labarrier_wait:
Tutti aspettano l'arrivo del
thread più lento
#define NT 10pthread_barrier_t barr;
int main(){pthread_t tid[NT];int i;
/* initialize barrier */pthread_barrier_init(&barr, NULL, NT);
for (i=0; i<NT; i++)pthread_create(&tid[i], NULL, &task, (void*)i));
/* do some useful work */
return 0;}
Barrier
e inizializzata con il numero dithread che devono sincronizzarsi
Una barriera deve essere dichiarata
Sync at barrier
int pthread_barrier_wait (pthread_barrier_t *barr);
Blocca il thread chiamante fino a che un numero di threadpari a quello specificato in pthread_barrier_init abbiaeseguito la pthread_barrier_wait.
In caso di successo, la funzione restituisce il valorePTHREAD_BARRIER_SERIAL_THREAD ad un thread nonspecificato e 0 agli altri thread. Quindi, la barriera vieneresettata allo stato dell'ultima pthread_barrier_init.
In caso di insuccesso, viene restituito un codice d'errore.
La sincronizzazione su una barriera avviene attraverso lafunzione pthread_barrier_wait:
void *task(void *arg){int rc;int i = (int)arg;
/* do some useful work */
/* synchronization point */rc = pthread_barrier_wait(&barr);if ((rc != 0) &&
(rc != PTHREAD_BARRIER_SERIAL_THREAD)) {printf("Could not wait on barrier\n");exit(-1);
}
/* do some useful work */
pthread_exit(NULL);}
Thread sync at barrier
14/05/2014
14
struct itimerspec {
struct timespec it_interval;
struct timespec it_value;};
Timer periodici
Tali funzioni notificano gli eventi attraverso un filedescriptor (fd) e usano la seguente struttura, definita in<sys/timerfd.h>:
int timerfd_create();int timerfd_gettime();int timerfd_settime();
periodo del timer
scadenza iniziale
Funzioni disponibili
int timerfd_create(int clk_id, int flag);
int timerfd_gettime(int fd, struct itimerspec *t);
Memorizza in t il valore del timer specificato da fd.
Il campo it_value di t contiene l'intervallo mancantealla successiva scadenza del timer.
Il campo it_interval di t contiene il periodo del timer.
Crea un timer di tipo clk_id e restituisce il file descriptorcorrispondente. L'argomento flag deve essere posto a zero.
Funzioni disponibili
int timerfd_settime(int fd, int flag,struct itimerspec *new_value,struct itimerspec *old_value);
Imposta il clock specificato da fd al valore new_value.
new_value specifica periodo e scadenza iniziale deltimer. Se scadenza iniziale = 0, il timer viene disabilitato.Se periodo = 0, il timer scatta solo una volta.
old_value restituisce l'impostazione del timer almomento della chiamata.
Se flag = 0, viene creato un timer relativo, se flag =TFD_TIMER_ABSTIME, viene creato un timer assoluto.
Funzioni disponibili
size_t read(int fd, void *buf, size_t n);
Tenta di leggere n byte dal file descriptor fd nel buffer buf.Se usata sul file descriptor di un timer, sospende il threadchiamante fino alla scadenza del timer.
Se il timer è già scattatto una o più volte, tale numeroviene restituito in buf.