



























Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Appunti sui thread e la sincronizzazione
Tipologia: Appunti
1 / 35
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!




























Scambio di dati tra processi (imparentati o no) Non condividono dati variaibili: Sono necessari meccanismi dedicati alla condivisione/scambio dati Lo scambio dati è limitata ai segnali solo per la sincronizzazione Esistono meccanismi addizionali per consentire lo scambio/condivisione dati tra processi (shared memory, sockets)
La gestione di più processi concorrenti (imparentati o no) richiede: Operazioni di context-switching costose (con intervento del kernel)
Nel caso di processi imparentati padre-figlio, la creazione di più processi figlio concorrenti implica Un aumento sensibile della memoria utilizzata
Per risolvere i problemi che nascono dalla limitazione dei processi, esistono i threads.
Una soluzione che supera tutti i problemi precedenti è rappresentata dai threads:
Utilizzo di un unico spazio di indirizzamento (dunque senza context-switching) Tracce multiple di esecuzione (concorrenti) all’interno di tale spazio di indirizzamento La creazione di un pool di threads avviene con tempi e risorse di gran lunga inferiori a quelle relative ai processi figlio Condivisione immediata di variabili statiche e possibilità di disporre anche di variabili locali private
Def: THREAD
Un thread è una sequenza di operazioni che si fanno nel computer contemporaneamente, ovvero operazioni che vengono eseguite in parallelo.
thread —> sequenza di tracce.
Esempio: il main è un thread, fino ad ora abbiamo costruito programmi che hanno un solo thread.
Un processo può essere composto da più threads Un processo è costituito per default da un thread Un thread è un flusso di esecuzione a se stante all’interno dello stesso processo Un thread condivide il proprio spazio di indirizzamento con gli altri thread del processo Perchè appartiene allo stesso processo Diversi thread possono eseguire le stesse funzioni di un processo o funzioni diverse
Ogni processo può contenere illimitati thread.
Il vantaggio dei processi multithread —> è che possono condividere dati e lavorare in parallelo.
Risorse condivise Per i thread la condivisione dati è automatica Se un thread altera una variabile non locale, la modifica è vista da tutti gli altri thread Prestazioni: Creare un thread è 10-100 volte più veloce che generare un processo Il cambio di contesto esiste anche per i thread ma è minimo.
Non esiste protezione tra i thread di uno stesso processo Tutti sono eseguiti nello stesso spazio degli indirizzi e la protezione da parte del SO è impossibile Un processo figlio può essere terminato indipendentemente dagli altri
I SO attuali sono multithread Sono composti da “kernel thread” dedicati a specifici servizi del SO I “Kernel thread” vengono gestiti dal kernel
Il kernel ha una tabella dei thread e dei processi Il kernel schedula i thread singolarmente Sui sistemi multicore/multiprocessore i thread concorrenti possono essere eseguiti in parallelo, perchè il sistema può assegnare thread diversi a diverse unità di calcolo
(In Linux i thread si chiamano task)
Abbiamo visto che il kernel di un SO gestisce interamente i processi utente e le relative aree dati (PCB), ma nel caso di processi utente conenenti “Threads” cosa succede?
Tre opzioni: Modello Molti-a-uno Il sistema operativo gestisce solo i processi utente. E’ necessario un sistema a livello utente per la gestione dei thread (unclusa schedulazione) Modello uno-a-uno Modello molti-a-molti
Adesso i sistemi operativi usano il modello UNO-A-UNO
Gli user thread sono gestiti dal kernel
(l’opposto dei processi)
Nel threading sincrono il thread dei aspettare la terminazione degli altri thread come accade per i processi padre-figlio
LA LIBRERIA Pthread Parte 1
La libreria Pthread contiene tutte le estensioni per i thread.
Esempio: non è possibile confrontare due identificatori direttamente ma si deve usare la funzione booleana phtread_equal()
Opaco vuol dire che non è definito in nessun tipo, infatti se volessimo stampare con il printf il TID non è possibile in quanto non è un intero e quindi non sappiamo come farlo. A differenza di pid_t che sappiamo essere l’alias di un interno int.
Ritorna l’identificatore di thread del thread chiamante Può essere utilizzata da un thread per autoidetificarsi
CREAZIONE E TERMINAZIONE DEI
THREAD
All’inizio ogni processo include solo un thread Per creare un nuovo kernel thread si utilizza pthread_create() Al kernel thread deve essere associato l’user thread, che deve consistere in una funzione definita nel programma La funzione deve avere al massimo un parametro (puntatore generico) e può tornare un qualunque valore (puntatore generico) Il numero massimo di chiamate alla phtread_create() è indefinito e dipende dall’implementazione
int phtread_create( pthread_t *tid; pthread_attr_t *attr, void (startRoutine)(void *), void *arg );
tid: indirizzo dell’identificatore generato attr: attributi del thread NULL corrispinde agli attributi di default startRoutine: Funzione eseguita dal nuovo thread arg: argomento passato alla funzione
int phtread_create( pthread_t tid, pthread_attr_t attr, void(startRoutine)(void *), void *arg );
Passaggio del parametro alla funzione
Il parametro formale e il parametro attuale sono puntatori a void (generico) Dunque, nella funzione phtread_create() il parametro attuale deve essere: qualunque cosa che sia un indirizzo al quale deve essere applicato il cast (void*)
Se vogliamo passare una stringa:
char str = "CIAO"; phtread_create(...,...,startRoutine, (void)str);
Nel quarto parametro non posso passare solo str ma devo passare IL PUNTATORE!
Se vogliamo passare un array di interi:
int arr[100]; phtread(...,...,startRoutine, (void*)arr);
Se vogliamo passare uno struct
struct thread info{ int a; float b; }info; phtread(...,...,startRoutine, (void*)&info);
La regola è sempre che dobbiamo passare un puntatore con il cast a void*.
Adesso vediamo che dopo che faccio la conversione in void* per utilizzare la variabile che passiamo dobbiamo nuovamente fare una conversione al contrario:
Come utilizzare il valore passato (parametro attuale) nella funzione routineStart? Dentro la funzione startRoutine devo riconvertire il parametro attuale nel tipo di appartenenza all’origine
Se passiamo una stringa:
void *startRoutine(void * value) { char str = (char)value; }
Se passiamo un array di int:
void *startRoutine(void * value){ int *vett = (int *) value; }
Se passiamo un intero:
Un intero processo (con tutti i suoi thread) termina se: Un suo thread effettua una exit() Ad esempio il main(), che è il primo thread viene terminato
Un singolo thread termina se: Esegue una return() Esegue una phtread_exit() Subisce una phtread_cancel() da un altro thread
Attenzione: sia la return() che la phtread_exit() devono tornare un void!*
phtread_exit()
void phtread_exit(void *valuePrt) ;
Permette ad un thread di terminare restituendo un valori di ritorno valuePtr questo valore può essere letto da qualunque altro thread, utilizzando un API phtread_join()
Parametri:
valuePtr deve essere un puntatore a quello che si vuole, ma deve essere un puntatore! Se si desidera che il thread non torni nulla, allora basta porre valuePtr=NULL
Per ogni kernel thread il SO associa delle risorse (es. stack) Quando un kernel termina queste risorse dovrebbero essere liberate, ma non solo per default
Un thread può essere eseguito in due modi:
Un thread Detache automaticamente rilascia le risorse allocate alla sua terminazione Non è necessario (anzi non ha alcun effetto) il fatto che un altro thread invochi la phtread_join() Per rendere Detached un thread è necessario l’uso di un’altra System Call da invocare dopo la creazione del thread pthread_detach();
Threading asincrono Richiede l’uso di thread Detached
Esempio:
int pthread_detach (phtread_t tid);
Alla sua creazione è per default un thread di tipo joinable Dopo la sua creazione, è possibile invocare la phtread_detach(), che permette di cambiare lo stato del thread da Joinable a detached Parametro; identificatore del thread (TID) che deve passare nel modo detached.
Valore di ritorno: 0 in caso di successo Un codice di errore in caso di fallimento