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
© Copyright 2024 Paperzz