Meccanismo a granularita' piu' fine per lo scheduling per le computazioni concorrenti. Il thread rappresenta l'unita' di base per l'utilizzo della CPU e consiste di un program counter un insieme di registri e di uno stack. I thread sono associati a gruppi. Ogni gruppo forma un task in cui i thread associati condividono lo spazio di memoria (codici e dati) e le risorse del sistema operativo (file aperti e segnali).
Esistono 2 diverse metodologie di implementazione dei thread:
- Lo scheduler non e' a conoscenza dei thread e vede solo processi - Tramite una libreria (a livello utente) viene realizzato uno "scheduler intraprocess" che suddivide il tempo di CPU assegnato al processo tra i vari thread che lo compongono. - Benefici: rende asincrone le chiamate a syscall bloccanti.
- Il sistema operativo mette a disposizione un insieme di syscall analoghe a quelle che si usano per la gestione dei processi. - In context switching e` piu` oneroso rispetto allo user level multithreading. - Benefici: permette l'esecuzione contemporanea di piu` thread dello stesso task.
Le ultime versioni del kernel forniscono la syscall clone() che viene utilizzata per implementare l'interfaccia utente a livello kernel. Il supporto dei thread a livello kernel rappresenta una delle principali differenze rispetto a Unix. Il thread e’ quindi l’unita’ di scheduling. Un processo tradizionale e’ quindi un thread che non condivide le risorse con altri thread.
La syscall clone() e’ specifica di Linux e quindi non e’ portabile.
man clone
Per rendere piu’ standard e flessibile l’interfaccia ai Thread esistono diverse librerie. L'interfaccia piu' diffusa e' quella Posix (libreria pthread). Questa interfaccia e’ integrata nella libreria glibc:
rpm –ql glibc | grep thread nm /lib64/libpthread.so.0 | grep " pthread"
Principali funzioni delle librearia pthread:
chiamante puo’ catturare con pthread_join()
Legge (ret_val) il valore di ritorno del thread terminato. Se la funzione termina correttamente il valore di ritorno e` 0
Il mutex viene utilizzato per sincronizzare l’accesso (in mutua esculsione) ad una sezione critica da parte di piu’ thread: il thread prima di entrare nella sezione critica (ad esempio per modificare una struttura dati) fa il lock del mutex associato, dopo la modifica fa l’unlock.
Se si accettano gli attributi di default si utilizza NULL.
La variabile condizione e’ uno strumento di sincronizzazione che consente ai thread di sospendere la propria esecuzione in attesa di un condizione logica. Ad ogni variabile condizione si associa una coda di attesa.
#define MAX 100 // numero massimo di utenti (thread) della risorsa int N=0; // numero corrente di utenti della risorsa pthread_cond_t C; // variabile condizione pthread_mutex_t M; // mutex process() { pthread_mutex_lock(&M); // fase di entrata If (N==MAX) pthread_cond_wait(&C,&M); // test and sleep N++; // aggiorna numero N pthread_mutex_unlock(&M); //Accesso alla risorsa pthread_mutex_lock(&M); // fase di uscita N--; // aggiorna numero N pthread_cond_signal(&C); // sveglia thr in coda pthread_mutex_unlock(&M); }
http://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread.h.html
Test dei thread: threadtest.c
Programma sorgente: pi_thr.c
Script di sottomissione: thread.bash
Comando di sottomissione: qsub -q didattica -l nodes=1:ppn=8 thread.bash
Se T1 è il tempo di esecuzione dell'algoritmo sequenziale e TP è il tempo dell'algoritmo parallelo con P processi, misuriamo il rendimento della parallelizzazione come: Speedup= T1/ TP oppure Efficienza= T1/ (TP*P) = SpeedUp/P
Idealmente Speedup coincide con P, mentre l'Efficenza è pari a 1.
Questo andamento non e' realistico per 2 motivi:
Generare il plot dell'isoefficenza in funzione de numero di thread (da 1 a 16) del programma pi_thr.
Comandi utili:
echo $EXE $ARGS 1>&2 eval $EXE $ARGS | grep -v "#" | awk {'print $4'}