Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Threads e Sincronizzazione, Appunti di Sistemi Operativi

Appunti sui thread e la sincronizzazione

Tipologia: Appunti

2020/2021

Caricato il 09/02/2023

lmingegneria
lmingegneria 🇮🇹

2 documenti

1 / 35

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
I TRHEAD 1
󾠱
I TRHEAD
Limiti dei processi
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.
Dai processi ai threads
Una soluzione che supera tutti i problemi precedenti è rappresentata dai threads:
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23

Anteprima parziale del testo

Scarica Threads e Sincronizzazione e più Appunti in PDF di Sistemi Operativi solo su Docsity!

I TRHEAD

Limiti dei processi

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.

Dai processi ai 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.

THREADS: I vantaggi

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.

SVANTAGGI:

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

KERNEL MUltiTHREAD

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)

GESTIONE DEI THREADS NEI SO

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

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.

Funzione ptrhead_self()

Ritorna l’identificatore di thread del thread chiamante Può essere utilizzata da un thread per autoidetificarsi

ESEMPIO:

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

pthread_create()

int phtread_create( pthread_t *tid; pthread_attr_t *attr, void (startRoutine)(void *), void *arg );

PARAMETRI

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:

TERMINAZIONE DEI THREAD

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

ESEMPIO:

TERMINAZIONE DEI THREAD SINCRONO E ASINCRONO

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:

TERMINAZIONE DEI THREAD MODO DETACHED

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

ESEMPIO: