1
5 Thread
• Ad un generico processo, sono associati, in maniera univoca, i seguenti dati e le seguenti informazioni:
– codice del programma in esecuzione
– un’area di memoria contenente le strutture dati dichiarate nel programma in esecuzione
– file aperti
– stack (per le chiamate di procedure e funzioni) – contenuto dei registri della CPU
2
5 Thread
• Consideriamo due processi che devono lavorare sugli stessi dati. Come possono fare, se ogni processo lavora su un’area di memoria diversa?
– i dati possono essere scambiati mediante messaggi o memoria condivisa, ma questa soluzione è inefficiente perché richiede l’intervento del SO (vedremo queste cose in pratica nel corso di laboratorio)
– i dati possono essere tenuti in un file che viene acceduto a turno dai due processi. Ancora più inefficiente!
3
5 Thread
• Ad esempio, in un editor di testo:
– un processo gestisce l’input e i comandi di formattazione dell’utente
– un altro processo esegue il controllo automatico di errore
• I due processi dovrebbero lavorare sullo stesso testo • La copia corrente del testo è mantenuta in MP, ma
come fanno i due processi a condividere la stessa copia, se ogni processo ha un diverso spazio di indirizzamento?
• Un normale processo è contraddistinto da un unico
Thread (filo) di computazione
codice dati file
registri stack
thread
5
5 Thread
• Un processo Multi-Thread è fatto di più thread, detti peer thread. Un insieme dei peer thread è detto task.
6
5 Thread
• Ad ogni thread è associato in modo esclusivo il suo stato della computazione, fatto da:
– valore del program counter e degli altri registri della CPU – uno stack
• Ma un thread condivide con i suoi peer thread:
– il codice in esecuzione – i dati
7
5 Thread
• La differenza fondamentale tra un processo e un insieme di peer thread (o task), è quindi che:
– la vita di un processo si sviluppa lungo un unico percorso (o “filo”) di computazione, fatto della sequenza di istruzioni eseguite dal processo.
– la vita di un task è fatta di più thread, ognuno dei quali è contraddistinto dal proprio “filo” di computazione
8
5 Thread
• Se c’è una sola CPU, il context switch avviene anche tra ognuno dei thread di un task, in modo che tutti possano portare avanti la computazione • Ma il context switch fra peer thread richiede il
salvataggio e il ripristino solo dei registri della
CPU e dello stack (che sono diversi per ogni
thread)
• Codice, dati e file aperti sono gli stessi per tutti, e
non devono essere cambiati: IL CONTEXT SWITCH TRA PEER THREAD E’ MOLTO PIU’ VELOCE
9
5 Thread
• Il context switch tra processi è molto più oneroso per il SO del context switch tra peer thread, perché nel primo caso sono coinvolte più informazioni da cambiare.
• Per questa ragione, i normali processi sono spesso chiamati: heavy-weight process (HWP)
• Mentre per un singolo thread si usa spesso il termine: light-weight process (LWP)
11
5 Processi e Thread
(a) Tre HWP: ad ognuno corrisponde un unico thread (b) Un task (o processo) con tre LWP (threads)
12
5 Processi e Thread
(a) (b)
(a) Item condivisi da tutti i thread di un processo (b) Item privati di ciascun thread
13
5 Thread
• Ma come si fa a distinguere tra i thread di un task (in modo da far fare a ciascuno una cosa diversa), se questi condividono lo stesso codice?
• Con delle opportune funzioni di libreria (system call), che all’interno del codice permettono di creare i thread necessari, e specificano per ciascun thread quale pezzo di codice deve eseguire quel thread
14 int n, in, out;
type item = ...; item buffer [n];
void *produttore( ); // contiene il codice del thread produttore void *consumatore( ); // contiene il codice del thread consumatore main ( ) /* produttore e consumatore */
{ ...
pthread_create(produttore); // crea il thread produttore pthread_create(consumatore); // crea il thread consumatore }
void *produttore ( ) {
qui il codice del produttore }
void *consumatore ( ) {
qui il codice del consumatore }
ora le variabili sono automaticamente condivise
Il main specifica quanti thread ci sono nel task e associa ad ogni thread il suo codice il codice del produttore è lo stesso di prima il codice del consumatore è lo stesso di prima 15
5 Thread a livello utente
• In alcuni sistemi, il SO non “sa” dell’esistenza dei threads. Si dice che i thread esistono solo a livello utente.
• Il context switch da un thread all’altro (all’interno dello stesso task) avviene mediante opportune funzioni di libreria chiamate all’interno del codice dei thread stessi.
• Quando un thread sta per fermarsi, specifica prima quale peer thread deve proseguire la computazione
• Il SO vede un task fatto di più thread come un normale processo, e gli assegna la CPU secondo il criterio di scheduling adottato.
• All’interno del task, il tempo di CPU è suddiviso tra i vari peer thread a seconda del codice di cui sono fatti.
• Se un thread si blocca senza farne partire un altro, tutto il tempo di CPU assegnato a quel task (e quindi a tutti i peer thread) viene sprecato
17
5 Thread a livello utente
18
5 Thread a livello kernel
• Il SO deve mantere delle strutture dati per gestire sia i processi che i thread
• Quando un thread si blocca, o termina il suo quanto di tempo, è il SO che assegna la CPU:
– a un altro thread dello stesso task – a un altro thread di un altro task – a un altro processo normale
19
5 Thread a livello kernel
20
5 Differenze tra kernel e user-level
thread
• La commutazione fra thread user-level non richiede l’intervento del SO ed è quindi più veloce
• La commutazione fra thread richiede il SO, ma un thread bloccato non blocca tutto il task • Molti SO supportano entrambi i tipi di thread
21
5 Vantaggi nell’uso dei thread
• Efficienza: in Solaris, creare un LWP richiede circa 30 volte meno tempo che creare un HWP. Il context switch tra peer thread richiede 5 volte meno tempo del context switch tra processi
• Condivisione di dati e risorse: più thread possono lavorare sugli stessi dati senza intervento del SO • Uso di architetture con più CPU: i thread sono naturalmente adatti per lavorare su più CPU in parallelo che condividono la stessa MP