Modello Thread Sistemi Operativi Mattia Natali
1
Modello Thread
Concetto di thread:
Attività parallela che consente “strutturalmente” un grado di cooperazione elevato.
Sono delle attività parallele che si formano in un medesimo processo, sono pensati per attività parallele che debbano interagire, comunicare tra di loro. Mentre i processi sono nati per avere un elevato grado di indipendenza.
Si può definire come “processo leggero”, una sua versione semplificata e si rende più efficiente la gestione e la comunicazione tra vari thread.
I processi limitano il grado di comunicazione ad un intero di 256bit in uscita.
I thread sono dei flussi di attività svolti in parallelo, sono delle funzioni (sottoprogrammi).
Essi condividono lo stesso ambiente del processo, condividono le variabili globali e statiche (il modello a processi no: le varie modifiche alle variabili non influenzano i vari processi). Il problema sta nel parallelismo dei vari thread.
Programmazione concorrente: evoluzione della programmazione sequenziale, cooperazione dei vari threads.
I thread, a differenza dei processi, sono tutti sullo stesso piano.
Tutti i thread di un processo condividono la stessa memoria dati, anche se ogni processo ha pila propria per le variabili locali.
Thread POSIX:
È uno dei tanti standard dei thread che variano in base ai sistemi operativi.
Le librerie di funzioni che creano i vari thread tenendo lo standard POSIX si chiama pthread.
Noi creeremo i vari thread all’interno di un solo processo:
Il processo può attivare vari threads.
Quando il processo termina, tutti i thread vengono chiusi forzatamente.
Ogni thread ha il suo identificatore di thread, di tipo pthread_t:
Gli identificatori sono univoci e sono diversi dai pid_t.
Ogni thread può essere posto in attesa di un evento.
Il thread termina quando esegue la return() o alla fine del codice eseguibile della funzione, esso può restituire un codice di terminazione al thread che lo ha creato.
Il main è il thread principale o di default.
Terminologia:
Esecuzione sequenziale: date due “attività” A e B nel codice di un programma, esse sono
sequenziali se, esaminando il codice del programma stesso, è possibile prevedere se A verrà svolta sempre prima di B oppure se B verrà svolta sempre prima di A.
Esecuzione concorrente: date due “attività” A e B nel codice di un programma, esse sono
concorrenti se, esaminando il codice del programma stesso, NON è possibile prevedere se A verrà svolta sempre prima di B oppure se B verrà svolta sempre prima di A.
Esecuzione parallela: si usa questo termine come sinonimo di concorrente, perché ci si riferisce a macchine con processore singolo. Per noi parallelo e concorrente saranno sinonimi perché eseguiremo i thread su un singolo processore.
Modello Thread Sistemi Operativi Mattia Natali
2
Funzioni thread POSIX:
Creazione del thread pthread_create():
pthread_create() è simile a fork().
Il thread viene creato passandogli il nome della funzione che esso deve eseguire ed esso parte sempre dall’inizio della funzione passata.
Prototipo: pthread_create (&tID1, NULL, &tf1, (void *) 1)
Gli argomenti sono 4:
• Indirizzo della variabile che conterrà l’identificatore del thread di tipo pthread_t.
• Puntatore agli attributi del thread: NULL significa standard.
• Passaggio dell’indirizzo di una funzione che si desidera eseguire.
• Ultimo è un argomento di tipo void * che viene passato alla funzione.
Attesa di terminazione tra thread pthread_join():
pthread_join() è simile a waitpid().
Prototipo: pthread_join (tID1, (void *) &thread_exit)
Argomenti:
• Variabile di tipo pthread_t che contiene l’identificatore del thread da attendere.
• Valore di uscita del thread.
Si deve sempre specificare il thread di cui si vuole attendere la terminazione.
Bisogna usarlo per garantire la terminazione dei thread creati.
Differenza processi e thread:
Efficienza: i processi sono più onerosi rispetto ai thread, perché deve creare tutte le proprie strutture dati per essere eseguito. I thread sono migliori in termini di efficienza.
Protezione: se un thread va in errore causa la chiusura forzata dell’intero processo, mentre i processi non si influenzano tra loro.
Cambiamento eseguibile: può essere effettuata solamente dai processi. I thread possono ricevere solamente una funzione dell’eseguibile stesso, non può cambiare codice.
Condivisione dati: le variabili globali dei thread sono comuni, la condivisione tra processi è più complicato.
Test programma concorrente:
Simulazioni: ci mostra alcuni comportamenti ma non tutti. Ciò è molto più utile nell’esecuzione sequenziale perché sappiamo l’ordine di esecuzione.
Tecniche formali a base matematica o tecniche di logica anche se è molto complesso.
Tecniche di programmazione:
Sequenze critiche:
• È una successione di istruzioni eseguite da più thread in parallelo che non devono essere mescolate tra loro affinchè il sirultato sia sempre corretto.
• Successione sequenziale di operazioni/istruzioni eseguite dal thread usando la nomenclatura ti.j = istruzione j eseguita dal thread i. Così sappiamo per certo che le istruzioni j del thread i sono eseguite sequenzialmente.
• Per avere le sequenze critiche bisogna assicurarsi che i thread avvengano in sequenza: se un qualsiasi thread comincia l’esecuzione della sequenza tutta la sequenza deve essere
Modello Thread Sistemi Operativi Mattia Natali
3
eseguita da quel thread si riduce la concorrenza. È di mutua esclusione, nessun’altra sequenza critica avviene insieme ad un’altra.
Istruzioni atomiche: se si riesce a garantire che l’esecuzione dell’istruzione non possa essere interrotta (indivisibile).
• I singoli statement NON sono atomici, perché quando vengono trascritti in linguaggio macchina un singolo statement può essere suddiviso.
Deallock o stallo.
Sincronizzazione.
Tecniche programmazione:
Mutua esclusione (Mutex):
Costrutti specializzati della libreria pthread, il costrutto si chiama mutex che garantisce la sequenzializzazione della sequenza critica.
Quando un thread attiva un blocco, eventuali altri thread che tentano di accedere al blocco vengono messi in attesa finché il primo non libera o rilascia il blocco.
La primitiva lock è una primitiva bloccante.
Passaggi:
• Dichiariamo variabile pthread_mutex_t.
• Inizializziamo tramite la funzione pthread_mutex_init.
• Blocchiamo e sblocchiamo la sequenza critica con pthread_mutex_lock e pthread_mutex_unlock.