6 slide/pagina

14/05/2014
Sommario
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
Processo
Processo
Un processo rappresenta l'unità di esecuzione in un SO.
 I processi non condividono memoria!
Programma: entità passiva descritta dal codice sorgente.
 Processi diversi
 eseguono codici diversi;
 accedono a spazi di memoria distinti.
Processo:
entità attiva determinata dall'esecuzione del
programma su un particolare insieme di dati.
 Essi possono comunicare mediante un meccanismo a
scambio di messaggi messo a disposizione dal SO:
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, ...
P1
P2
channel
dati
Processi e thread
dati
Funzioni
 Un processo può essere costituito da più sottoprocessi
concorrenti, detti thread.
 pthread_create: crea un thread
 I thread di uno stesso processo
 condividono lo stesso spazio di memoria;
 hanno stack distinti;
 possono eseguire lo stesso codice. 1 2
 pthread_exit: termina il thread chiamante
Esempio di processo
composto da 3 thread:
 pthread_cancel: termina un altro thread
3 Pi
 pthread_join: aspetta la terminazione di un thread
Compilazione
Per utilizzare la libreria, inserire #include <pthread.h>
dati
 gcc prova.c -o prova -lpthread -lrt
1
14/05/2014
Thread creation
int
pthread_create(thread_t
pthread_attr_t
void
void
*id,
*attr,
*(*body)(void *),
*arg);
Thread creation
Esempio:
Thread ID
usa attributi
di default
nessun
argomento
pthread_t tid;
Crea un thread:
pthread_create(&tid, NULL, task, NULL);
id
memorizza l'identificatore unico del thread creato.
attr
puntatore a una struttura che definisce gli attributi del
thread (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.
void
{
*task()
printf("I am a simple thread.\n");
}
Thread ID
Thread creation
Poiché l'identificatore di thread viene restituito a chi
effettua la create, esistono le seguenti funzioni:
Main thread
 pthread_self()
restituisce l'ID del thread chiamante
pthread_t th1;
 pthread_equal(tid1, tid2)
restituisce un valore  0, se tid1 = tid2, 0 altrimenti.
pthread_create(&th1,…
pthread_t tid1, tid2;
tid1 = pthread_self();
if (pthread_equal(tid1,tid2))
return 0;
else return 1;
Dopo la create, non è detto che il thread sia attivo, esso ha
solo un ID e le strutture dati necessarie. Può accadere che:
 il thread non sia ancora partito;
 sia attivo in coda pronti;
 sia già terminato.
Thread termination
Thread termination
Un thread può terminare per diverse cause:
void pthread_exit(void *retval);
• dopo che viene eseguita l'ultima istruzione della
funzione ad esso associata (terminazione normale);
restituito dal thread terminato.
• quando esso chiama la primitiva pthread_exit.
int
• quando viene terminato da un altro thread attraverso la
primitiva pthread_cancel.
• quando termina il processo a cui appartiene, nel nostro
caso il main(), a causa di una terminazione normale o di
una chiamata alla primitiva exit.
thread th1
Termina il thread chiamante e restituisce in retval il valore
pthread_cancel(pthread_t th);
Invia una richiesta di cancellazione per il thread th. La
terminazione 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
2
14/05/2014
Thread joining
E' possibile attendere la terminazione di un thread
attraverso la funzione pthread_join:
int
pthread_join(pthread_t th, void **retval);
Aspetta la terminazione del thread indicato da th.
Esempio - create
#include <pthread.h>
void
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
• Se il thread è già terminato, *retval assume il valore
PTHREAD_CANCELED.
printf("Thread1 returns %d\n", tret1);
printf("Thread2 returns %d\n", tret2);
return 0;
}
Esempio - create
void
{
int
*task(void *p)
*pi;
pi = (int *)p;
printf("This is TASK %d\n", *pi);
}
vengono creati 2 thread con lo
stesso codice, che si differenziano
mediante il parametro passato.
tret1 = pthread_create(&tid1, NULL, task, (void*)&a);
tret2 = pthread_create(&tid2, NULL, task, (void*)&b);
• Se retval  NULL, il valore di ritorno del thread
terminato viene copiato in *retval.
• Retistuisce 0 in caso di successo, o un codice di errore.
*task(void *p);
int main()
{
pthread_t tid1, tid2;
int
tret1, tret2;
int
a = 1, b = 2;
Joinable vs. detached
Quando si chiama la pthread_join è possibile che il thread
sia già terminato, quindi il sistema deve mantenere delle
informazioni anche dopo la terminazione.
 Tali informazioni vengono distrutte (e la memoria
liberata) con l'operazione di join.
 Se non ci si deve sincronizzare con un thread e non si
chiama la pthread_join, la memoria non viene liberata.
 Per evitare spreco di memoria quando la join non è
richiesta, un thread può essere dichiarato di tipo
detached (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 sistema
libera automaticamente tutte le strutture dati di sistema da
esso utilizzate.
Detached threads
int
pthread_detach(pthread_t th);
Definisce il thread th come detached. Retistuisce 0 in caso
di successo, o un codice di errore.
Tale funzione può essere chiamata anche dallo stesso
thread che si vuole definire come detached.
In tal caso, l'identificatore del thread può essere recuperato
chiamando la funzione pthread_self():
pthread_t my_id;
my_id = pthread_self();
pthread_detach(my_id);
3
14/05/2014
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.
Thread state
Lo stato del thread che deve essere creato viene definito
modificando gli attributi di default per mezzo della
funzione pthread_attr_setdetachstate():
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
Gli attributi devono essere inizializzati e distrutti:
int
pthread_attr_init(pthread_attr_t *attr);
int
pthread_attr_destroy(pthread_attr_t *attr);
pthread_attr_setdetachstate(&attr,
PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, task, NULL);
Linux Scheduling
Thread priority
La priorità di un thread viene specificata mediante una
struttura contenente un solo campo:
struct sched_param {
int sched_priority;
};
La priorità deve essere prima assegnata per mezzo di una
struttura locale e poi inserita negli attributi del thread
mediante un'apposita funzione:
struct sched_param
 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 vengono
inseriti i thread con la stessa priorità.
 Il primo thread della coda a più alta priorità viene
selezionato come running task:
priority
CPU
mypar;
mypar.sched_priority = 23;
pthread_attr_setschedparam(&myatt, &mypar);
Linux Scheduling
Linux prevede tre tipologie di scheduler (per ogni thread):
SCHED_OTHER (Default Policy)
Scheduler Round Robin con meccanismo di aging.
SCHED_FIFO
Scheduler prioritario: thread a pari priorità vengono
gestiti con politica FIFO.
SCHED_RR
Scheduler prioritario: thread a pari priorità vengono
gestiti con politica Round‐Robin.
SCHED_FIFO e SCHED_RR sono dette politiche real time
e possono essere usate solo da root.
Politiche real time
SCHED_FIFO (Fixed‐Priority Scheduling + FIFO)
Scheduler prioritario: thread a pari priorità vengono
gestiti con politica FIFO. Un thread viene eseguito fino a
terminazione, cancellazione, bloccaggio, o preemption.
SCHED_RR (Fixed‐Priority Scheduling + RR)
Scheduler prioritario: thread a pari priorità vengono
gestiti con politica Round‐Robin. Un thread viene
eseguito fino a terminazione, cancellazione, bloccaggio,
preemption o esaurimento del quanto temporale.
Il quanto dipende dal sistema e non può essere definito
dall'utente, ma può essere conosciuto chiamando la
funzione sched_rr_get_interval().
4
14/05/2014
Linux Scheduler
Round-Robin quantum
int sched_rr_get_interval(pid_t pid, struct timespec *tp);
Scrive nella struttura puntata da tp il valore del quanto
temporale utilizzato dallo scheduler Round‐Robin per il
processo identificato da pid.
Se pid = 0, viene restituito il valore del quanto utilizzato per
il processo chiamante:
#include <sched.h>
 Per usare politiche diverse è necessario comunicare al
kernel tale intenzione attraverso la funzione
pthread_attr_setinheritsched:
pthread_attr_setinheritsched(&myatt, PTHREAD_EXPLICIT_SCHED);
printf("Q: %ld s, %ld ns\n",
q.tv_sec, q.tv_nsec);
pthread_attr_setschedpolicy(&myatt, SCHED_FIFO);
Protezione temporale
Esempio - create
/* attribute structure
/* priority structure
/* thread id
myatt;
pthread_attr_init(&myatt);
sched_rr_get_interval(0, &q);
myatt;
mypar;
tid;
 Per default, un thread viene schedulato con la stessa
politica usata per il thread padre, ossia SCHED_OTHER.
pthread_attr_t
struct timespec q;
int
main()
{
pthread_attr_t
struct sched_param
pthread_t
Anche lo scheduler viene definito attraverso gli attributi,
utilizzando la funzione pthread_attr_setschedpolicy.
*/
*/
*/
Per evitare che applicazioni errate blocchino il sistema,
esiste un meccanismo di protezione temporale che
garantisce al sistema una minima quantità di tempo.
Tale meccanismo è configurabile attraverso 2 parametri:
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);
/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 alle
attività real‐time in ogni periodo. Default = 950000 µs.
}
Protezione temporale
Dunque, per default, il 95% della CPU è riservato alle
attività real time e il 5% a quelle gestite con SCHED_OTHER.
 Ciò significa che un thread ad alta priorità può essere
interrotto anche se schedulato con SCHED_FIFO.
 La percentuale riservata ai thread real time può essere
modificata o disabilitata (solo da root):
> 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%.
5
14/05/2014
Rappresentazione del tempo
Tipi di clock
Linux supporta i seguenti clock:
CLOCK_REALTIME: è il valore più
vicino possibile al tempo assoluto.
Tuttavia, esso può essere discontinuo
in quanto può subire aggiustamenti.
CLOCK_MONOTONIC: rappresenta il
tempo trascorso a partire da un
imprecisato istante. Esso non è
affetto da aggiustamenti, per cui è la
soluzione migliore per misurare il
tempo intercorso tra due eventi.
Nello standard POSIX, il tempo è rappresentato per mezzo
della seguente struttura, definita in <time.h>:
struct timespec {
time_t tv_sec;
long
tv_nsec;
}
 Il tipo time_t dipende dall'implementazione, ma di
solito è un intero a 32 bit.
 Purtroppo la libreria standard non fornisce funzioni di
supporto per compiere operazioni sul tempo, per cui è
necessario definirsi delle funzioni ausiliarie.
Copia di tempi
Tale funzione copia il tempo sorgente ts nella variabile
destinataria puntata da td:
void time_copy(struct timespec *td,
struct timespec ts)
{
td->tv_sec = ts.tv_sec;
td->tv_nsec = ts.tv_nsec;
}
/* seconds */
/* nanoseconds */
Somma di millisecondi
Tale funzione somma al tempo t un valore ms espresso in
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;
}
}
Confronto di tempi
Tale funzione confronta due tempi t1 e t2 e restituisce
0 se uguali, 1 se t1 > t2, ‐1 se t1 < t2:
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;
}
Funzioni disponibili
int
clock_getres(clockid_t clk_id, struct timespec *res);
Se res  NULL, scrive in res la risoluzione del clock
specificato in clk_id. Questa dipende dalla particolare
implementazione 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.
6
14/05/2014
Funzioni disponibili
int
clock_nanosleep(clockid_t clk_id, int flag,
const struct timespec *t, struct timespec *rem);
Esempio
struct timespec t;
struct timespec dt;
int
delta = 500;
Sospende l'esecuzione del thread chiamante finché il clock
clk_id non raggiunge il tempo specificato in t.
/* absolute time */
/* time interval */
/* delay in milliseconds */
clock_gettime(CLOCK_MONOTONIC, &t);
time_add_ms(&t, delta);
 Se flag = 0, il tempo t viene interpretato come relativo al
tempo corrente;
/* suspend until absolute time t */
clock_nanosleep(CLOCK_MONOTONIC,
TIMER_ABSTIME, &t, NULL);
 Se flag = TIMER_ABSTIME, il tempo t viene interpretato
come assoluto.
/* suspend for a relative interval of 500 ms */
dt.tv_sec = 0;
dt.tv_nsec = delta*1000000;
clock_nanosleep(CLOCK_MONOTONIC, 0, &dt, NULL);
 Se il thread viene risvegliato prima del tempo impostato,
il tempo rimanente viene memorizzato in rem.
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 di
risposta (che varia da job a job).
Un thread periodico
void
*task(void *arg)
{
struct timespec t;
int
period = 100;
/* period in milliseconds */
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);
clock_gettime(CLOCK_MONOTONIC, &t);
time_add_ms(&t, period);
while (1) {
while (1) {
/* do useful work */
/* do useful work */
clock_nanosleep(CLOCK_MONOTONIC,
TIMER_ABSTIME, &t, NULL);
time_add_ms(&t, period);
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);
}
}
}
}
7
14/05/2014
Thread periodici
Struttura di parametri
Un thread periodico, dunque, segue il seguente schema:
Conviene definire una struttura per i parametri del thread:
void *task(void *p)
{
<local state variables>
struct
int
long
int
int
int
int
struct
struct
};
set_period();
while (1) {
<thread body>
if (deadline_miss()) <do action>;
wait_for_period();
task_par {
arg;
wcet;
period;
deadline;
priority;
dmiss;
timespec at;
timespec dl;
/*
/*
/*
/*
/*
/*
/*
/*
task argument
in microseconds
in milliseconds
relative (ms)
in [0,99]
no. of misses
next activ. time
abs. deadline
*/
*/
*/
*/
*/
*/
*/
*/
 Tale struttura deve essere inizializzata da chi effettua
la thread_create e passata come argomento al thread.
}
}
 Occorre definire una stuttura per ogni thread.
Esempio: main
struct sched_param mypar;
struct task_par tp[NT];
array di variabili
pthread_attr_t att[NT];
per tutti i thread
pthread_t
tid[NT];
for (i=0; i<NT; i++) {  Se noto, il WCET può essere
tp[i].arg = i;
utilizzato per il test di garanzia.
tp[i].period = 100;

La deadline assoluta viene
tp[i].deadline = 80;
impostata online appena noto
tp[i].priority = 20;
tp[i].dmiss = 0;
l'istante di attivazione.
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]);
}
Esempio: thread
void *task(void *arg)
{
<local state variables>
struct
task_par
*tp;
tp = (struct task_par *)arg;
i = tp->arg;
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 di
attivazione e la deadline assoluta del task.
NOTA: il timer non viene impostato per interrompere.
recupera
l'argomento
set_period(tp);
while (1) {
<thread body>
if (deadline_miss(tp))
printf("!");
wait_for_period(tp);
}
}
set_period()
void set_period(struct task_par *tp)
{
struct timespec t;
recupera il puntatore a
task_par
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 successiva
e, al risveglio, ricalcola l'istante di attivazione e la deadline.
NOTA: anche se il thread esegue la time_add_ms() in un
istante maggiore di quello di risveglio, il calcolo
risulta corretto.
8
14/05/2014
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, altrimenti
restituisce 0.
Sincronizzazione fra thread
In generale, esistono
sincronizzare più thread:
Join
i
seguenti
meccanismi
Thread Joining
per
Main thread
thread 1
consente di aspettare la terminazione di uno o
più thread.
consente di
esclusione
eseguire
codice
in
Condition
consente di riattivarsi quando una variabile
condivisa raggiunge un valore prefissato.
Barrier
consente a più thread di sospendersi fino a
che tutti abbiano eseguito una certa
operazione.
thread 2
pthread_create()
pthread_create()
Semaphore consente mutua esclusione e sincronizzazione.
Mutex
consente di aspettare la terminazione di uno o più thread
mutua
pthread_exit()
pthread_join()
pthread_join()
Semaphores
pthread_exit()
Inizializzazione semafori
I semafori generali non sono parte della libreria pthread,
ma sono definiti nello standard POSIX:
int sem_init (sem_t *sem, int pshared, unsigned int v);
 Per utilizzarli occorre includere il file <semaphore.h>.
Inizializza un semaforo:
 Un semaforo è una variabile di tipo sem_t
Funzioni principali:
 sem_init:
 sem_destroy:
 sem_wait:
 sem_post:
 sem_getvalue:
inizializza un semaforo.
libera la memoria di un semaforo non
più utilizzato.
wait su semaforo.
signal su semaforo.
restituisce il valore del semaforo.
sem
indirizzo del semaforo;
pshared se 0, indica che il semaforo è condiviso fra
thread (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.
9
14/05/2014
Uso semafori
Uso semafori
int sem_wait (sem_t *sem);
int sem_destroy (sem_t *sem);
Se il valore corrente del semaforo è 0, blocca il thread
chiamante finché non viene eseguita una sem_post,
altrimenti decrementa il semaforo ed esce.
Dealloca la memoria utilizzata per il semaforo.
int sem_getvalue (sem_t *sem, int *pval);
int sem_post (sem_t *sem);
Se esistono thread bloccati sul semaforo, risveglia quello a
più alta priorità, altrimenti incrementa il semaforo ed esce.
Entrambe le funzioni restituiscono 0 in caso di successo, ‐1
in caso di errore.
Legge il valore del semaforo e lo scrive nella variabile
puntata da pval.
Entrambe le funzioni restituiscono 0 in caso di successo, ‐1
in caso di errore.
Esempio semafori
Sincronizzazione su evento
Valore iniziale = 0
sem_t event;
#include <semaphore.h>
sem_init(&event, 0, 0);
sem_t sem1, sem2, sem3;
int
{
/* definisce 3 semafori
*/
thread 1
main()
/* inizializza sem1 per la sincronizzazione
sem_init(&sem1, 0, 0);
*/
/* inizializza sem2 per la mutua esclusione
sem_init(&sem2, 0, 1);
*/
thread 2
sem_post(&event)
sem_wait(&event)
/* inizializza sem3 per l'accesso simultaneo */
/* di max 4 thread ad una risorsa con 4 unità */
sem_init(&sem3, 0, 4);
Mutua esclusione
struct point {
int x;
int y;
} p;
void *writer();
void *reader();
sem_t
s;
Struttura globale condivisa dai
thread in mutua esclusione.
void
{
}
Inizializzazione del semaforo
void
{
/* do some useful work
*/
*reader()
sem_wait(&s);
printf("(%d,%d)\n", p.x, p.y);
sem_post(&s);
pthread_create(&tid1, NULL, writer, NULL);
pthread_create(&tid2, NULL, reader, NULL);
}
*writer()
sem_wait(&s);
p.x++;
p.y++;
sem_post(&s);
Definizione del semaforo
int
main()
{
pthread_t
tid1, tid2;
sem_init(&s, 0, 1);
Mutua esclusione
}
10
14/05/2014
Semafori Mutex
Inizializzazione Mutex
Prima di essere utilizzato, un mutex dev'essere dichiarato e
inizializzato. Esistono 3 modi per inizializzare un mutex:
Un MUTEX è un tipo particolare di semaforo binario con
alcune restrizioni (mirate a ridurre gli errori):
 può solo essere usato per la mutua esclusione, non
per la sincronizzazione;
 un mutex occupato da un thread può essere sbloccato
solo dallo stesso thread.
/* modo 1: inizializzazione diretta */
pthread_mutex_t
mux = PTHREAD_MUTEX_INITIALIZER;
Funzioni principali:
/* modo 2: inizializzazione con valori di default */
pthread_mutex_init(&mux, NULL);
 pthread_mutex_init:
inizializza un semaforo.
 pthread_mutex_destroy: libera la memoria di un mutex
che non serve più.
 pthread_mutex_lock:
wait su semaforo.
 pthread_mutex_unlock: signal su semaforo.
pthread_mutex_t
mux;
pthread_mutexattr_t matt;
Struttura globale condivisa dai
thread in mutua esclusione.
void *writer();
void *reader();
pthread_mutex_t
Nell'esempio riportato, i tre modi sono equivalenti e
inizializzano il mutex con i valori di default.
Esempio - Mutex
void
{
mux = PTHREAD_MUTEX_INITIALIZER;
pthread_create(&tid1, NULL, writer, NULL);
pthread_create(&tid2, NULL, reader, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
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.
*writer()
pthread_mutex_lock(&mux);
p.x++;
p.y++;
pthread_mutex_unlock(&mux);
Definizione e inizializzazione
del semaforo.
int
main()
{
pthread_t
tid1, tid2;
*/
*/
/* modo 3: inizializzazione con attributi */
pthread_mutexattr_init(&matt);
pthread_mutex_init(&mux, &matt);
Esempio - Mutex
struct point {
int x;
int y;
} p;
/* define a mutex
/* define attributes
}
void
{
*reader()
pthread_mutex_lock(&mux);
printf("(%d,%d)\n", p.x, p.y);
pthread_mutex_unlock(&mux);
}
Priority Inheritance
#define _GNU_SOURCE
pthread_mutex_t
mux1, mux2;
pthread_mutexattr_t matt;
/* define 2 mutexes
/* 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 per
inizializzare semafori diversi.
11
14/05/2014
Immediate Priority Ceiling
#define _GNU_SOURCE
pthread_mutex_t
mux1, mux2;
pthread_mutexattr_t matt;
/* define 2 mutexes
*/
/* define mutex attrib. */
Condition variables
Tale meccanismo consente ad un thread di attendere un
valore prefissato di una variabile condivisa:
Waiting thread
Signaling thread
pthread_mutexattr_init(&matt);
pthread_mutexattr_setprotocol(&matt, PTHREAD_PRIO_PROTECT);
pthread_mutexattr_setprioceiling(&matt, 10);
pthread_mutex_init(&mux1, &matt);
x = x + 1
pthread_mutexattr_setprioceiling(&matt, 15);
pthread_mutex_init(&mux2, &matt);
x != 8
Y
Y
sleep
N
x == 8
N
pthread_mutexattr_destroy(&matt);
Il loop è necessario in quanto il risveglio può essere dovuto ad altre cause
Se più thread sono in attesa, è possibile svegliarne solo uno oppure tutti insieme
 Il ceiling del semaforo deve essere uguale alla massima
priorità dei task che lo usano.
Condition variables
Condition variables
Sono sempre usate insieme a un mutex:
pthread_mutex_t
pthread_cond_t
int
Prima di bloccarsi, il mutex viene rilasciato
in modo atomico e riacquisito al risveglio.
int
{
Signaling thread
Waiting thread
/* define a mutex
/* define a cond. variable
/* variable to be checked
*/
*/
*/
main()
/* initialize variables with default attributes
pthread_mutex_init(&mux, NULL);
pthread_cond_init(&cv, NULL);
lock(mux);
x = x + 1;
if (x == 8) {
unlock(mux);
signal(cond_var);
}
else unlock(mux);
lock(mux);
while (x != 8) {
sleep(cond_var, mux);
}
unlock(mux);
mux;
cv;
count = 0;
/* do some useful work
*/
*/
/* reclaim memory
*/
pthread_mutex_destroy(&mux);
pthread_cond_destroy(&cv);
}
Waiting thread
void
{
Signaling thread
*watch_count()
void
{
/* wait until count reaches the THRESHOLD
*/
*inc_count()
/* do some useful work */
pthread_mutex_lock(&mux);
pthread_mutex_lock(&mux);
while (count < THRESHOLD) {
count++;
if (count == THRESHOLD) {
pthread_mutex_unlock(&mux);
pthread_cond_signal(&cv);
}
else pthread_mutex_unlock(&mux);
pthread_cond_wait(&cv, &mux);
}
pthread_mutex_unlock(&mux);
/* do some useful work
*/
/* do some useful work */
pthread_exit(NULL);
pthread_exit(NULL);
}
}
12
14/05/2014
Brodcasting
void
{
Note
*inc_count()
/* do some useful work */
pthread_mutex_lock(&mux);
count++;
Si possono risvegliare tutti i thread in attesa, mediante una broadcast
if (count == THRESHOLD) {
pthread_mutex_unlock(&mux);
pthread_cond_broadcast(&cv);
}
else pthread_mutex_unlock(&mux);
/* do some useful work */
int
int
pthread_cond_signal (pthread_cond_t *cond);
pthread_cond_broadcast (pthread_cond_t *cond);
 La signal risveglia uno dei thread bloccati sulla variabile
condition. Il thread svegliato dipende dallo scheduler
(per scheduler real time il thread svegliato è quello a
priorità più alta).
 La broadcast risveglia tutti i thread bloccati sulla
variabile condition.
 Entrambe le funzioni non hanno effetto se non ci sono
thread bloccati.
pthread_exit(NULL);
}
Barrier
Barrier
Ogni thread si blocca finché tutti abbiano eseguito la
barrier_wait:
thread 1
thread 2
thread 3
Tutti aspettano l'arrivo del thread più lento
#define NT 10
pthread_barrier_t barr;
int main()
{
pthread_t tid[NT];
int
i;
Una barriera deve essere dichiarata
e inizializzata con il numero di
thread che devono sincronizzarsi
/* initialize barrier
*/
pthread_barrier_init(&barr, NULL, NT);
barrier_wait()
for (i=0; i<NT; i++)
pthread_create(&tid[i], NULL, &task, (void*)i));
barrier_wait()
barrier_wait()
/* do some useful work
BARRIER
*/
return 0;
}
Sync at barrier
La sincronizzazione su una barriera avviene attraverso la
funzione pthread_barrier_wait:
int
pthread_barrier_wait (pthread_barrier_t *barr);
Thread sync at barrier
void *task(void *arg)
{
int rc;
int i = (int)arg;
/* do some useful work
Blocca il thread chiamante fino a che un numero di thread
pari a quello specificato in pthread_barrier_init abbia
eseguito la pthread_barrier_wait.
In caso di successo, la funzione restituisce il valore
PTHREAD_BARRIER_SERIAL_THREAD ad un thread non
specificato e 0 agli altri thread. Quindi, la barriera viene
resettata allo stato dell'ultima pthread_barrier_init.
In caso di insuccesso, viene restituito un codice d'errore.
*/
/* 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);
}
13
14/05/2014
Timer periodici
int
int
int
timerfd_create();
timerfd_gettime();
timerfd_settime();
Tali funzioni notificano gli eventi attraverso un file
descriptor (fd) e usano la seguente struttura, definita in
<sys/timerfd.h>:
struct itimerspec {
struct timespec it_interval;
periodo del timer
struct timespec it_value;
scadenza iniziale
};
Funzioni disponibili
int
timerfd_create(int clk_id, int flag);
Crea un timer di tipo clk_id e restituisce il file descriptor
corrispondente. 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.
int
timerfd_gettime(int fd, struct itimerspec *t);
Memorizza in t il valore del timer specificato da fd.
 new_value specifica periodo e scadenza iniziale del
timer. Se scadenza iniziale = 0, il timer viene disabilitato.
Se periodo = 0, il timer scatta solo una volta.
 Il campo it_value di t contiene l'intervallo mancante
alla successiva scadenza del timer.
 old_value restituisce l'impostazione
momento della chiamata.
 Il campo it_interval di t contiene il periodo del timer.
 Se flag = 0, viene creato un timer relativo, se flag =
TFD_TIMER_ABSTIME, viene creato un timer assoluto.
del
timer
al
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 thread
chiamante fino alla scadenza del timer.
 Se il timer è già scattatto una o più volte, tale numero
viene restituito in buf.
14