Thread
I thread, al contrario dei processi, condividono:
- lo spazio di indirizzamento, e quindi l'intera memoria del processo
- i file aperti
ma mantenendo comunque isolati:
- i registri, per eseguire codice diverso
- lo stack, per salvare variabili diverse
- le maschere dei segnali
Dato che la memoria è condivisa, la creazione di un thread è meno impegnativa di quella di un processo.
Il ciclo di vita è analogo ai processi, eccetto per la presenza di altri stati oltre a bloccato, ovvero waiting per l'attesa di un evento da un'altro thread e sleeping per l'attesa di un tempo specifico.
Oltre alle operazioni dei processi, supportano anche la:
- Cancellazione: richiesta di terminazione al thread, che però potrebbe mascherarla
- Join: il thread che richiede il join viene bloccato fino all'uscita del thread su cui viene richiesto
Su Linux, i thread e i processi sono entrambi chiamati task, infatti possono entrambi essere generati con la funzione clone
, che con i giusti parametri può avere lo stesso effetto di fork
.
Su Windows invece, esistono i fiber, cioè delle sottounità di esecuzione nel contesto del thread padre, che vengono prelasciati assieme al thread e schedulati saltando da un fiber ad un'altro.
Modelli di threading
Tra i modelli esistenti, i thread possono essere:
-
A livello utente (molti-a-uno)
In cui ogni thread vive all'interno dello stesso contesto di esecuzione del processo padre, e quindi ogni processo conterrà la sua tabella dei thread.
Questo permette ai S.O. senza thread interni di supportarli in modo portabile, ammettendo anche la scelta dell'algoritmo di scheduling dall'utente, ed evitando l'overhead del cambio di contesto.
Lo svantaggio sono le prestazioni limitate, perchè non sono schedulati su più processori dato che il kernel li vede come un singolo thread, ma anche il blocco del processo intero se un thread fa I/O.
-
A livello kernel (uno-a-uno)
In questa situazione ogni thread avrà il proprio contesto di esecuzione. Per cui il nucleo conterrà la tabella dei thread, oltre a quella dei processi, e applicherà le operazioni con chiamate di sistema.
Questo migliora le prestazioni ma riduce la portabilità, visto che ogni S.O. avrà interfacce diverse.
-
Ibridi (molti-a-molti)
Implementando un thread pool (i.e. un gestore di thread) si possono combinare i due precedenti, per cui per ogni processo i thread potrebbero appartenere allo stesso contesto o essere suddivisi su altri.
I contesti su cui sono eseguiti sono chiamati thread worker, e sono persistenti nel kernel.
Lo scheduling dei thread a livello utente avviene tramite l'intervento di un processore virtuale, assegnato al processo dal nucleo e notificato per passare a livello utente quando un worker si blocca.