Il 0% ha trovato utile questo documento (0 voti)
57 visualizzazioni113 pagine

Programmazione Concorrente - Teoria

Caricato da

Arwen
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
57 visualizzazioni113 pagine

Programmazione Concorrente - Teoria

Caricato da

Arwen
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 113

Programmazione

Concorrente
TPSI, 4BP
Recap - delle puntate precedenti…
Sistemi Operativi

- single-tasking
- primi sistemi: un job alla volta, modalità batch
- multi-tasking
- più processi in esecuzione, contemporaneamente
- sia applicazioni utente (word-processor, browser, mail …)
- che processi di sistema
- multi-threading
- sistemi più recenti: più flussi di esecuzione nel contesto di uno stesso processo
- un’applicazione può eseguire più compiti
- un browser scarica diversi file, mentre stampa una pagina …
- un'applicazione server può gestire più richieste in parallelo
Recap - delle puntate precedenti…
PARALLELISMO

- sistemi con un solo core


- il parallelismo di processi e thread viene simulato
- time slicing
- il tempo di elaborazione è suddiviso tra processi e/o thread
- allo scadere di ogni unità di tempo, il s.o. opera un cambio di contesto (context switch)
- sistemi con più processori o con più core
- in ogni istante di tempo, ci possono essere più processi o thread fisicamente in esecuzione
Piccolo spoiler
DEF.

- PROCESSO:
- per processo si intende un'istanza di un programma in esecuzione
- THREAD:
- un thread è una suddivisione di un processo in due o più filoni o sottoprocessi, che vengono eseguiti
concorrentemente
- ogni processo ha almeno un thread
- i thread condividono le risorse del processo (il suo contesto), compresa la memoria e i file aperti
- ma hanno il proprio stack, il proprio program counter…
Agenda
- Processi sequenziali e paralleli
- I processi e i loro stati
- Gli interrupt
- Creazione, sospensione e terminazione di processi
- Il Process Control Block (PCB)
- Il concetto di risorsa condivisa e le modalità di accesso
- I grafi di Holt
- Differenza tra processi e thread
- Single threading e multithreading
- I thread POSIX
- Processi non sequenziali e grafo di precedenza
- Regioni critiche e mutua esclusione
- Starvation e deadlock
- Esercizi di pratica
Il modello a processi - L1
TPSI, 4BP
Il modello a Processi
Il modello a Processi
Intro:

● I moderni sistemi operativi gestiscono il processore per sfruttare al massimo il parallelismo hardware e ottimizzare i
tempi di risposta.
● Il "throughput" indica la capacità del sistema di soddisfare le richieste di un numero elevato di processi.

Definizione di processo:

● Un processo è un'entità logica in evoluzione, ovvero un programma in esecuzione.


● Ogni processo è caratterizzato da:
○ Codice del programma da eseguire
○ Dati: variabili globali utilizzate durante l'esecuzione
○ Program counter: indirizzo dell'istruzione da eseguire
○ Registri CPU
○ Stack: parametri, variabili locali a funzioni/procedure
● In memoria, un processo è composto da una parte statica (codice) e una parte dinamica (dati e stack).
● Al processo possono essere associate altre risorse, come file aperti, connessioni di rete e dispositivi I/O.
Il modello a Processi
Il modello a Processi
Modello a processi:

● Nel modello a processi, tutto il software (incluso il sistema operativo) è organizzato in processi sequenziali.
● Un unico processore può essere condiviso tra più processi grazie a un algoritmo di schedulazione che
determina quando eseguire ogni processo.
● Questa tecnica, chiamata multiprogrammazione, permette l'esecuzione simultanea di più programmi in memoria.
● I processi possono essere indipendenti o cooperanti:
○ Processi indipendenti: evolvono autonomamente senza comunicazione tra loro.
○ Processi cooperanti: necessitano di scambiare informazioni per poter progredire.

Vantaggi della multiprogrammazione:

● Riduce i tempi morti della CPU, sfruttando al massimo le sue potenzialità.


● Migliora la risposta del sistema alle richieste degli utenti, soprattutto quando si eseguono diverse attività
contemporaneamente.
● Permette l'esecuzione di processi background che non richiedono l'interazione diretta dell'utente (ad esempio,
antivirus, backup).
Il modello a Processi
Considerazioni aggiuntive:

● Il sistema operativo gestisce la concorrenza tra processi per garantire un'esecuzione equa e sicura.
● Esistono diversi algoritmi di schedulazione con diverse proprietà e obiettivi (Round Robin, FCFS, con proprità…)
● La sincronizzazione è un meccanismo che permette ai processi di cooperare in modo ordinato e sicuro.
● La comunicazione tra processi avviene tramite meccanismi come la memoria condivisa, i messaggi e le pipe.
Il modello a Processi
Interazione tra processi:

● Processi cooperanti:
○ Si influenzano a vicenda o condividono dati → l’interazione consiste nello scambio di informazioni al fine di
eseguire un’attività comune
○ Permettono:
■ Parallelizzazione dell'esecuzione (ad esempio, su macchine multi-CPU).
■ Replicazione di un servizio (ad esempio, connessioni di rete).
■ Modularità di processi con funzioni diverse (ad esempio, correttore ortografico di Word).
■ Condivisione delle informazioni.

● Processi in competizione:
○ Ostacolano il reciproco completamento a causa di risorse limitate. → i processi interagiscono per
sincronizzarsi nell’accesso a risorse comuni
○ Esempi:
■ Schedulatore dei processi che compete per la CPU.
■ Processi in coda per la stampante.
Il modello a Processi
Definizioni:

● Processo cooperante: Può influenzare o essere influenzato da altri processi, condividendo dati.
● Processo in competizione: Può evolvere indipendentemente, ma entra in conflitto per le risorse.

Stato dei processi:

● Ciclo di vita: dal suo inizio alla terminazione, un processo passa attraverso diverse fasi:

○ Stato "nuovo": il processo è stato creato ma non ha ancora utilizzato la CPU


○ Stato "esecuzione": il processo sta eseguendo le sue istruzioni (utilizza la CPU)
○ Stato "attesa": il processo è in attesa di una risorsa per poter evolvere (wait list)
○ Stato "pronto": il processo è pronto ad accedere alla CPU ma attende il suo turno (è nella ready list in attesa
che gli venga assegnata la CPU)
○ Stato "terminato": il processo ha completato la sua esecuzione e le risorse utilizzate devono essere rilasciate.
Il modello a Processi
● Diagramma degli stati: rappresenta i passaggi tra gli stati.
● Creazione di un processo: Un processo è creato da un
processo "padre" e prende il nome di "figlio". Un'eccezione è il
processo "init" *, creato dal SO senza un "padre".

● Cambi di stato:
○ Da nuovo a pronto: Il processo viene inserito nella coda
dei processi pronti.
○ Da pronto a esecuzione: Il processo viene assegnato
alla CPU.
○ Da esecuzione a pronto o terminato: Il processo
termina il suo tempo di CPU o termina l'esecuzione.
○ Da esecuzione a sospeso: Il processo necessita di una
risorsa non disponibile e si sposta nella coda di attesa.
○ Da sospeso a pronto: La risorsa diventa disponibile e il
processo viene spostato nella coda dei processi pronti.

* init: è il primo processo che il kernel manda in esecuzione dopo che il


computer ha terminato la fase di bootstrap
*time slice: periodo di tempo massimo che un processo può
utilizzare la CPU prima di essere interrotto per dare spazio ad altri

Il modello a Processi processi

Sospensione per INTERRUPT:

● Lo schedulatore può bloccare un processo al termine del suo time slice e farne partire uno nuovo.
● Questo tipo di interruzione è chiamata "interrupt software" e comporta un cambio di contesto (context
switching) da parte del SO.
● Un comportamento diverso si verifica se la sospensione è causata da un'interruzione I/O:
○ Gli interrupt I/O sono detti "hardware" o "esterni" e sono organizzati in classi con un indirizzo specifico (interrupt
vector) nella memoria.
○ Se si verifica un'interruzione (ad esempio, un problema con il disco fisso) durante l'esecuzione del processo
xxx:
■ Il program counter, lo stato del programma e la maschera delle interruzioni di xxx vengono salvati nello
stack dall'hardware dedicato alle interruzioni.
■ La CPU salta all'indirizzo dell'interrupt vector che gestisce l'interruzione e salva lo stato del processo.
■ Viene eseguita la routine di risposta all'interruzione.
● Il context switching comprende tutte le operazioni di salvataggio/ripristino dei dati di un processo a causa di un
interrupt.
● Terminata la gestione dell'interrupt (interno o esterno), lo scheduler decide quale processo far partire e un piccolo
programma assembler carica i registri e la mappa di memoria per il nuovo processo corrente.
Il modello a Processi
● Il PCB rappresenta i processi nel sistema operativo, contenendo tutte le informazioni relative a ciascun
processo.

● Le informazioni contenute nel PCB includono:


● Identificatore unico (PID) del processo.
● Stato corrente del processo.
● Program counter, che indica l'indirizzo dell'istruzione corrente del processo.
● contenuto dei registri (SP, IR, accumultori).
● Informazioni di scheduling (priorità, puntatori alle code ...).
● Informazioni per gestore di memoria (puntatori alla memoria del processo, ai registri base, limite ...).
● Informazioni relative all’I/O (puntatori alle risorse allocate al processo, fi le aperti ...)
● Informazioni di accounting (tempo di CPU utilizzato ...).
● Il PCB svolge un ruolo fondamentale nella gestione dei processi all'interno del sistema operativo.
Il modello a Processi

Creazione e gestione del descrittore di processo:

● Allocato dinamicamente alla creazione del processo.


● Inizializzato con le informazioni del nuovo processo.
● Aggiornato al cambio di stato del processo.
● Rimosso alla terminazione del processo.

Process Table:

● Tabella mantenuta dal SO per contenere tutti i PCB dei processi attivi.
● Permette un rapido accesso alle informazioni su qualsiasi processo.
● Aggiornata al cambio di stato di un processo.
Panoramica dei comandi per la gestione dei processi
Comandi UNIX utilizzati per la gestione dei processi:
Panoramica delle funzioni per la gestione dei processi
Tali funzioni sono disponibili principalmente nei sistemi operativi basati su Unix, come Linux, macOS e altri sistemi
Unix-like. Tuttavia, alcuni di essi possono essere implementati anche in altri sistemi operativi, sebbene con comportamenti
o limitazioni leggermente diversi.
Le system call sono interfacce attraverso le quali i programmi utente possono comunicare con il kernel del sistema
operativo per richiedere servizi, come la creazione di un nuovo processo ( fork() ), l'esecuzione di un nuovo programma
(exec() ), l'attesa della terminazione di un processo figlio ( wait() e waitpid() ), la terminazione del processo corrente
(exit() ), la gestione dei segnali (signal() ) e altro ancora.
Panoramica delle funzioni per la gestione dei processi
Il modello a Processi
Creazione di un processo

Un processo (padre) può richiedere la creazione di un nuovo processo (figlio)


Il modello a Processi
Creazione di un processo - System call fork()

La chiamata a sistema fork() permette di creare un processo duplicato del processo genitore. La fork() crea un
nuovo processo che:

- condivide l’area codice del processo genitore I processi si differenziano usando il


- utilizza una copia dell’area dati del processo genitore valore restituito dalla fork():
- valore < 0 → Errore
- valore = 0 → Processo figlio
- valore > 0 → Processo
genitore e il valore è il PID
del processo figlio.
*NIX si riferisce ai sistemi operativi UNIX e XENIX, molto simili tra loro.

Il modello a Processi SYSTEM CALL: Una chiamata di sistema, indica il meccanismo usato da un processo
a livello utente o livello applicativo, per richiedere un servizio a livello kernel.

Nei sistemi operativi *NIX-like, la creazione di un processo avviene con la funzione:

pid fork(void); → system call

● All'atto della chiamata:


○ Viene generato un nuovo PID e un nuovo descrittore del processo (PCB) per il figlio.
○ I segmenti di dati vengono copiati nella memoria del nuovo processo, creando due copie identiche (il "figlio" è
un clone del "padre").
○ L'unica differenza tra padre e figlio è il valore restituito da fork():
■ Il padre ottiene il PID assegnato dal SO al figlio.
■ Il figlio ottiene il valore 0.
● Vengono chiusi i file aperti, rilasciata la memoria e salvato l'exit status nel descrittore del processo per il recupero da
parte del padre con la funzione wait() (descritta di seguito).
● Il PID non viene rilasciato e il descrittore non viene distrutto, ma al padre viene segnalata la terminazione del figlio.
Il modello a Processi
La dichiarazione

pid fork(void); → system call

in C dichiara una funzione chiamata fork che non accetta argomenti e restituisce un valore di tipo pid_t, che è
tipicamente il tipo utilizzato per rappresentare un identificatore di processo in molti sistemi Unix-like.

La funzione fork è utilizzata per creare un nuovo processo duplicando il processo chiamante. Il processo che
chiama fork diventa il processo padre, mentre il nuovo processo creato diventa il processo figlio. Entrambi i
processi continuano l'esecuzione dal punto in cui è stata chiamata la funzione fork, ma con valori di ritorno
differenti. Nella funzione padre, fork restituisce l'ID del processo figlio, mentre nel processo figlio restituisce 0.
Il modello a Processi
Sospensione di un processo padre:

Un processo padre può sospendere la propria attività in attesa che il figlio termini con l'istruzione:

pid wait (int* status);


● Il padre si sospende in attesa della terminazione di un figlio.
● Quando il figlio termina, il padre recupera il valore dello stato di uscita specificato dalla funzione exit() nel figlio.
● Il valore restituito è composto da due byte:
○ Byte meno significativo: indica la ragione della terminazione (naturale o segnale).
○ Byte più significativo: stato di uscita specificato da exit() nel figlio.
● Il SO rilascia il PID del figlio e rimuove il suo descrittore di processo.

Processi "zombie":
Se un processo figlio termina prima che il padre invochi wait(), il figlio diventa "defunct" o "zombie":

● Il processo è terminato, ma il descrittore non può essere rilasciato.


● Questo accade perché il processo figlio ha terminato la sua esecuzione, ma le risorse associate ad esso non sono
ancora state liberate completamente.
I processi zombie sono processi terminati ma in attesa che il genitore rilevi il loro stato di terminazione.
Il modello a Processi
Sospensione di un processo padre

La funzione

pid wait (int* status);


in C è una chiamata di sistema (system call) che viene utilizzata per attendere che uno dei processi figlio termini
l'esecuzione. Questa funzione blocca il processo chiamante fino a quando uno dei processi figlio non termina,
restituendo l'ID del processo figlio terminato.

L'argomento status è un puntatore a un intero dove verrà memorizzato lo stato di uscita del processo figlio terminato.
Questo stato può contenere informazioni su come è terminato il processo figlio, ad esempio se è terminato normalmente o
se è stato terminato da un segnale.
Il modello a Processi

Video da guardare:

- https://www.youtube.com/watch?v=FYVSQ82QCRk
Risorse e Condivisione - L2
TPSI, 4BP
Risorse e Condivisione
Introduzione

● I processi sono programmi in esecuzione che necessitano di risorse per funzionare.


● Il sistema di elaborazione può essere visto come un insieme di risorse da assegnare ai processi.
● Le risorse sono un elemento fondamentale dei sistemi operativi.
● La gestione efficiente delle risorse è necessaria per garantire il corretto funzionamento dei processi e del sistema nel
suo complesso.

Definizione di risorsa

● Una risorsa è qualsiasi componente hardware o software di cui un processo o il sistema ha bisogno.
● Le risorse possono essere riutilizzabili o meno.
● I processi competono per le risorse, che spesso sono scarse.

Classi e istanze di risorse

● Le risorse possono essere raggruppate in classi.


● Le risorse all'interno di una classe sono equivalenti (ad esempio, byte di memoria, stampanti dello stesso tipo).
● Le istanze di una classe sono le singole risorse concrete (ad esempio, celle di memoria, stampanti specifiche).
● La molteplicità di una risorsa è il numero massimo di istanze disponibili.
Risorse e Condivisione
Richiesta e assegnazione di risorse

● Un processo NON può richiedere una specifica istanza di una risorsa, ma solo una generica della sua classe.
● Il sistema operativo (SO) assegna al processo una qualsiasi istanza disponibile della classe richiesta.
● La molteplicità di una risorsa determina il numero massimo di processi che possono utilizzarla contemporaneamente.
● Se il numero di processi supera la molteplicità, la risorsa deve essere condivisa.

Esempio: CPU

● Un PC ha un solo processore, quindi la sua molteplicità è 1.


● Al massimo un processo può utilizzare la CPU alla volta.
● I processi che richiedono la CPU la condividono e competono per il suo utilizzo.
Risorse e Condivisione
Condivisione e gestione delle risorse
● La condivisione significa che le risorse vengono utilizzate da più processi in modo alternato.
● La condivisione permette ai processi di scambiarsi informazioni.
● Le risorse statiche vengono assegnate in anticipo (pianificazione), mentre quelle dinamiche vengono gestite
(controllate) durante l'esecuzione.
● Per le risorse con molteplicità finita, il SO controlla l'accesso per evitare conflitti.

Gestione delle risorse dal SO

● Il SO fornisce un gestore per ogni tipo di risorsa.


● Il gestore regola l'utilizzo della risorsa e definisce un protocollo di accesso.
● Il protocollo specifica come un processo richiede, ottiene, utilizza e rilascia una risorsa finché gli altri processi la
possano utilizzare.
Risorse e Condivisione
Relazione tra Processi e Risorse

● I processi competono per le risorse, inviando richieste per ottenere l'assegnazione di ciò che serve loro per l'esecuzione.
● L'interazione tra risorse e processi può essere classificata in base a:
○ Tipo di richiesta
○ Modalità di assegnazione

Classificazione delle Richieste

● In base al numero:
○ Singola: Richiesta di una singola risorsa di una specifica classe (es: un processore richiede una sola risorsa alla
volta).
○ Multipla: Richiesta di una o più risorse da una o più classi (es: un processo richiede contemporaneamente CPU e
memoria).
● In base al tipo:
○ Richiesta bloccante: Il processo si sospende e attende la risorsa se non disponibile immediatamente. La
richiesta viene accodata e riconsiderata dal gestore quando la risorsa è libera.
○ Richiesta non bloccante: Il processo continua l'esecuzione se la risorsa non è disponibile. Riceve una notifica
e può decidere come procedere.
Risorse e Condivisione
Classificazione dell'Assegnazione

● Statica: La risorsa viene assegnata al processo alla sua creazione e rimane dedicata fino alla terminazione (es:
descrittore di processo, area di memoria RAM).
● Dinamica: Assegnazione frequente durante la vita del processo, tipica per risorse condivise (es: periferiche I/O).

Classificazione delle Risorse

● In base alla mutua esclusività:


○ Seriali: Accesso esclusivo, i processi si mettono in coda per utilizzarle (es: stampanti, CD-ROM). RISORSA con
accesso MUTUAMENTE ESCLUSIVO (un solo processo alla volta può utilizzare/ accedere alla risorsa).
○ Non seriali: Accesso contemporaneo da più processi, considerate di molteplicità infinita (es: file a sola lettura).

● In base alla modalità di utilizzo:


○ Prerilasciabili (o preemptive): Possono essere sottratte al processo durante l'utilizzo senza danni (stato salvabile
e ripristinabile). Il processo riprende dal punto in cui è stato interrotto (es: processore, memoria).
○ Non prerilasciabili (o non-preemptive): Non possono essere sottratte senza causare danni al processo in
esecuzione (es: stampante, masterizzatore).
Risorse e Condivisione
Risorse Private

● Un processo può avere anche risorse private, assegnate in modo esclusivo dal SO e non visibili agli altri processi.
● Queste risorse non richiedono gestione in condivisione.

GRAFO DI HOLT

Il Grafo di Holt, introdotto da Richard C. Holt nel 1972, è un metodo di rappresentazione grafica per descrivere lo stato di
allocazione delle risorse tra processi in un sistema operativo. È particolarmente utile per identificare situazioni di deadlock, in cui
i processi si bloccano a vicenda in attesa di risorse.
Risorse e Condivisione
Struttura del Grafo

Il Grafo di Holt è un grafo bipartito orientato, composto da due tipi di nodi:

● Nodi risorsa: rappresentati da nodi quadrati (o rettangolari per classi di risorse o risorse multiple). Ogni nodo risorsa ha
un'etichetta che ne identifica il tipo.
● Nodi processo: rappresentati da nodi circolari. Ogni nodo processo ha un'etichetta che ne identifica il nome.

I nodi sono collegati da archi orientati che rappresentano le relazioni tra risorse e processi:

● Arco di assegnazione: un arco orientato da un nodo risorsa a un nodo processo (R → P) indica che la risorsa è
attualmente assegnata al processo.

● Arco di richiesta: un arco orientato da un nodo processo a un nodo risorsa (P → R) indica che il processo ha richiesto la
risorsa ma non gli è stata ancora assegnata.
Risorse e Condivisione
Proprietà del Grafo

Il Grafo di Holt possiede le seguenti proprietà:

● Grafo orientato: gli archi hanno una direzione ben definita, indicando il flusso di richieste e assegnazioni di risorse.
● Grafo bipartito: non esistono archi che collegano nodi dello stesso tipo (risorsa-risorsa o processo-processo).
● Presenza di cicli: un ciclo nel grafo può indicare una situazione di deadlock, in cui i processi si bloccano a vicenda in
attesa di risorse.
Risorse e Condivisione
Grafo di Holt - supponiamo ora di avere classi di risorse con molteplicità diversa.

È importante sottolineare come nei grafi di Holt non si rappresentino le richieste che possono essere soddisfatte ma solo
quelle pendenti: quindi le frecce uscenti dai processi verso le risorse (quelle indicate in rosso) indicano le “risorse
mancanti” ai processi per evolvere, che sono in quel momento assegnate ad altri processi.
Risorse e Condivisione
Riducibilità di un GRAFO di Holt
● I grafi di Holt sono utilizzati per rappresentare la situazione di allocazione delle risorse tra processi in un sistema operativo.
● Spesso è utile analizzare l'evoluzione futura del sistema, eliminando situazioni in cui un processo è in grado di terminare e
liberare risorse.
● Questo viene ottenuto tramite la creazione di un GRAFO RIDOTTO, che elimina i nodi che rappresentano processi non
bloccati.

Concetto di riducibilità:

● Un grafo di Holt è definito riducibile se esiste almeno un nodo processo con solo archi entranti (risorse assegnate) e
nessun arco uscente (richieste in sospeso).
● Un nodo processo con solo archi entranti rappresenta un processo che non necessita di ulteriori risorse per terminare.
● L'eliminazione di questo nodo e dei suoi archi entranti dal grafo ridotto riflette la futura disponibilità delle risorse da esso
detenute.
Risorse e Condivisione
Riducibilità - ESEMPI (1)
Risorse e Condivisione
Riproducibilità - ESEMPI (2)
Risorse e Condivisione
Riproducibilità - ESEMPI (2)

Ricorda!
Un grafo di Holt si dice riducibile se esiste almeno un
nodo processo con solo archi entranti
Riduzione consiste nell'eliminare tutti gli archi di tale
nodo e riassegnare le risorse ad altri processi
Qual è la logica? eventualmente, un nodo che utilizza
una risorsa prima o poi la rilascerà; a quel punto, la
risorsa può essere riassegnata
Risorse e Condivisione
Esercizi
I thread o "processi leggeri" - L3
TPSI, 4BP
I thread o "processi leggeri"
I thread o "processi leggeri"
Un thread (o processo leggero) è un’unità di esecuzione che condivide codice e dati con altri
thread ad esso associati.
I thread o "processi leggeri" - panoramica
Caratteristica Processi Thread
Unità di esecuzione che racchiude il codice, i dati e le risorse Unità di esecuzione all'interno di un processo che condivide le
Definizione di un programma. risorse con altri thread dello stesso processo.
Numero di thread Un processo ha almeno un thread, ma può averne di più.
I processi in genere sono indipendenti e utilizzano diverse
Condivisione delle aree di memoria. La comunicazione avviene tramite I thread all'interno di un processo condividono in genere le stesse
risorse meccanismi di comunicazione del sistema operativo. informazioni di stato, la memoria e altre risorse di sistema.
La creazione di un nuovo processo è onerosa per il sistema
Creazione (context switching). La creazione di un thread è molto più veloce e meno costosa.
Vantaggi - Migliore isolamento in caso di errori. - Maggior sicurezza. - Migliore utilizzo del processore. - Maggior velocità di risposta.
- Minor isolamento in caso di errori. - Minore sicurezza. - La
condivisione delle risorse accentua il pericolo di interferenza - Gli
- Maggior consumo di risorse. - Maggior overhead (costo accessi concorrenti devono essere sincronizzati di modo da evitare
Svantaggi aggiuntivo) di comunicazione. interferenze (thread safeness)
- Browser Web, Server che gestiscono richieste multiple da utenti
Esempi di utilizzo - diversi.
Supporto del sistema Può essere supportato dal sistema operativo o da librerie di
operativo Richiede il supporto del sistema operativo. programmazione.
Strutture dati
associate PCB (Process Control Block). TCB(Thread Control Block)
Creazione Da parte del sistema operativo o dall'utente. Da parte del codice all'interno di un processo.
Chiamate di sistema CreateProcess (Windows), fork() e exec() (Unix). CreateThread (Windows), thr_create (Unix).
I thread o "processi leggeri"
Intro
● Le applicazioni con elaborazione parallela variano molto (videogiochi, social network, controllo processi, server
internet).
● Il progettista necessita di strumenti per descrivere diverse situazioni di parallelismo:
○ Alto parallelismo con molte risorse condivise.
○ Cooperazione ridotta con attività parallele indipendenti e poche interazioni/aree di memoria condivise.

Processi e thread:

● Processi: entità autonome per attività autonome con poche risorse condivise.
● Thread: nuova entità con caratteristiche per problemi con alta cooperazione e condivisione di risorse.
● I thread sono più adatti a "realizzare specifiche attività" piuttosto che un "compito completo".
● Un thread è un “segmento di codice” (tipicamente una funzione) che viene eseguito in modo sequenziale
all'interno di un processo pesante.
I thread o "processi leggeri"
Processi "pesanti" e "leggeri":

● Processi pesanti: quelli descritti finora.


● Processi leggeri (thread): Un thread è un flusso di controllo che può essere attivato in parallelo ad altri thread
nell’ambito di uno stesso processo e quindi nell’esecuzione dello stesso programma.

Struttura di un processo pesante:

● Immagine del processo:


○ Process ID.
○ Program Counter.
○ Stato dei Registri.
○ Stack.
○ Codice.
○ Dati.
● Risorse utilizzate:
○ File aperti.
○ Processi figli.
○ Dispositivi di I/O.
I thread o "processi leggeri"
Spazio di indirizzamento: (si intende l’insieme dell’immagine di un processo e le risorse da esso possedute)

● Ogni processo ha un proprio spazio di indirizzamento.


● Due processi non condividono alcuna area di memoria.
● Neanche i processi figli condividono le variabili dei processi padri.

Cambio di contesto (context switch):

● Quando un processo si sospende, il SO esegue diverse operazioni complesse:


○ Salvare il contesto del processo sospeso. (come se fotografasse la situazione attuale)
○ Ripristinare il contesto del processo che inizia/riprende l'esecuzione.
● Le operazioni di context switch richiedono tempo di CPU "sprecato" ("overhead").
● L'overhead è il "costo di gestione" per il multitasking.
● Il context switch per i processi è complesso: i processi sono "pesanti" perché richiedono elaborazioni "pesanti" per
passare dallo stato di pronto a quello di esecuzione.
● L'obiettivo del SO è ridurre al minimo l'overhead migliorando gli algoritmi di scheduling. → min{overhead}
Se due (o più) processi potessero condividere dati e codice, il context switch fra di loro sarebbe molto più veloce.
Per soddisfare questo tipo di esigenza è̀ nato il concetto di thread
I thread o "processi leggeri"
● I thread sono più leggeri perché il context switch è meno costoso per loro.
● Il SO gestisce i thread all'interno di uno spazio di indirizzamento del processo.
● Esistono diversi tipi di thread con differenti caratteristiche e modalità di gestione.

Processi leggeri:

● Un processo può essere visto come l'unione di due componenti:


○ Esecuzione del codice: il programma che condivide la CPU con altri processi.
○ Risorse utilizzate: quelle comprese nello spazio di indirizzamento del processo.
● Il SO gestisce questi due componenti in modo indipendente.

● Questa osservazione è alla base dei thread:


○ La parte del processo che esegue il codice viene chiamata thread leggero o LWP (Light Weight Process).
○ La parte del processo che possiede le risorse viene chiamata processo pesante o processo/task.
I thread o "processi leggeri"
Definizione di thread:

● Un thread è un "segmento di codice" (tipicamente una funzione) che viene eseguito in modo sequenziale all'interno
di un processo pesante.
● Tutti i thread di un processo condividono le sue risorse, risiedono nello stesso spazio di indirizzamento e hanno
accesso a tutti i suoi dati.
● Definizione: un thread è un flusso di controllo che può essere attivato in parallelo ad altri thread all'interno dello
stesso processo e quindi nell'esecuzione dello stesso programma.

Multithreading:

● Il termine multithreading indica la molteplicità di flussi di esecuzione all'interno di un processo pesante.


● I thread all'interno di un processo pesante hanno il vantaggio di condividere lo spazio di indirizzamento, le strutture
dati e le variabili.
I thread o "processi leggeri"
Struttura di un thread:

● Per evolvere parallelamente agli altri thread o processi, ogni thread ha un proprio
set di elementi che lo caratterizzano, simile al PCB (Process Control Block) dei
processi tradizionali, chiamato TCB (Thread Control Block).

● Il TCB include:
○ Un identificatore di thread (ID).
○ Un program counter.
○ Un insieme di registri.
○ Uno stato di esecuzione (running, ready, blocked).
○ Un contesto che viene salvato quando il thread non è in esecuzione.
○ Uno stack di esecuzione.
○ Uno spazio di memoria privato per le variabili "locali".
○ Un puntatore al PCB del processo contenitore.
I thread o "processi leggeri"
Spazio di memoria dei thread:

● I thread NON hanno un'area dedicata per il codice del programma, in quanto condividono quello del processo
che li genera e l'area dati.

Variabili locali dei thread:

● È possibile definire variabili "personali" per ogni singolo thread, con accesso tramite chiave.
● In questo modo, nessun altro thread "fratello" o processo può visualizzare o modificare queste variabili.

Vantaggi dei thread:

● Condivisione di memoria: a differenza dei processi (pesanti), un thread può condividere variabili con altri
(appartenenti allo stesso task)
● Minor costo di context switch: TCB di thread non contiene alcuna informazione relativa a codice e dati : cambio di
contesto fra thread dello stesso task ha un costo notevolmente inferiore al caso dei processi pesanti
● L'utilizzo dei thread offre la possibilità di sfruttare meglio le architetture multiprocessore.
● Permette una comunicazione e uno scambio di informazioni immediati tra i thread.
I thread o "processi leggeri"
Rientranza delle routine di libreria:

● L'esecuzione dei thread richiede che le routine di libreria siano rientranti.


● "Rientrante" si riferisce alla capacità di una routine o di una funzione di essere eseguita in modo sicuro da più thread
contemporaneamente, senza causare conflitti o corruzione dei dati. Le routine di libreria rientranti sono progettate
per essere sicure per il threading, consentendo ai programmi di utilizzare i thread in modo efficace senza rischi di
danneggiare i dati condivisi o il funzionamento del programma stesso.
● Una funzione è considerata rientrante se può essere interrotta in qualsiasi punto e quindi eseguita in
modo sicuro in un altro thread senza causare problemi come corruzione della memoria o comportamenti
imprevedibili.
● I thread di un programma interagiscono con il SO mediante system call che utilizzano dati e tabelle di sistema
dedicate al processo.
● Queste devono essere progettate per essere utilizzate da più thread contemporaneamente senza perdita di dati.
I thread o "processi leggeri"
I thread o "processi leggeri"
Non tutti i codici si prestano a essere eseguiti come thread, cioè in grado di realizzare un sistema multi-thread: i programmi
(o le funzioni) devono essere avere la proprietà di thread safeness.

Thread safety → un programma o segmento di codice è detto thread safe se garantisce che nessun thread possa
accedere a dati in uno stato inconsistente , vale a dire che in caso di esecuzioni multiple viene garantito che nessun
thread possa accedere a dati mentre questi vengono modificati/aggiornati.

Consistenza →la consistenza è una proprietà dei dati/delle variabili che richiede a ogni transazione di agire su di essi
in modo corretto.

La mutua esclusione significa che due o più


processi o thread non possono accedere
contemporaneamente alla stessa
risorsa condivisa. Serve a evitare
conflitti e problemi di consistenza dei dati
quando più entità cercano di modificare la
stessa risorsa simultaneamente. Utilizzando
tecniche di sincronizzazione come semafori o
mutex, si garantisce che solo un'entità alla
volta possa accedere alla risorsa,
mantenendo l'integrità dei dati condivisi.
Processi Thread

Il tempo necessario per creare un La creazione di un thread richiede


processo è inferiore rispetto a un generalmente meno tempo.
thread.

Il processo è un programma in Il thread rappresenta una sezione del


esecuzione. processo.

Un processo rimane isolato. Il thread condivide la memoria.

Un processo richiede molto tempo Un thread impiega meno tempo per il


per il cambio di contesto. cambio di contesto.

Se un processo viene bloccato, l'altro Se un thread a livello utente viene


processo rimane inalterato. bloccato, tutti gli altri thread rimangono
inalterati.

Un processo è considerato un'entità Un thread è considerato un'entità


pesante. leggera.

C'è il coinvolgimento della chiamata Nessun coinvolgimento della chiamata


di sistema. di sistema
I thread o "processi leggeri"

Single threading vs multithreading


Vi sono diverse modalità di gestione dei thread all'interno di un sistema operativo, vi è il single threading e il
multithreading. Analizziamo i quattro scenari possibili basati sulla combinazione di processi e thread singoli o
multipli.

Scenari di gestione dei thread

1. Singolo processo, thread singolo: Questo scenario è tipico di sistemi operativi come MS-DOS, dove
esiste un solo processo utente e un solo thread.
2. Singolo processo, thread multiplo per processo: In questo scenario, un singolo processo gestisce
più thread. È tipico di ambienti come il supporto runtime di Java (JVM).
3. Multiplo processo, thread singolo per processo: In questo caso, più processi hanno un solo thread.
È tipico di sistemi operativi come UNIX.
4. Multiplo processo, thread multiplo per processo: Questo scenario combina più processi con più
thread per processo. È tipico di sistemi operativi come Linux, Windows NT e Solaris.
I thread o "processi leggeri"
Single threading vs multithreading
Single threading vs multithreading

Il diagramma mostra il processo a


thread singolo e il processo a thread
multipli. Un processo a thread singolo
è un processo con un singolo thread. Un
processo multi-thread è un processo
con più thread. Come è possibile notare,
i thread multipli in esso contenuti hanno i
propri registri, stack e contatore ma
condividono il codice e il segmento
dati.
Esempio di multithreading

Esempio di un server web: più thread consentono di soddisfare più richieste contemporaneamente,
senza dover soddisfare le richieste in sequenza o biforcare processi separati per ogni richiesta in
arrivo. (Quest'ultimo è il modo in cui questo genere di cose veniva fatto prima che fosse sviluppato il concetto
di thread).
Single threading vs multithreading
I thread o "processi leggeri"
Approfondimenti sul multithreading

Il multithreading si implementa in due modalità: a livello utente (User-Level) e a livello kernel (Kernel-Level).

Multithreading a livello utente (User-Level)

In questa modalità, i thread sono gestiti da librerie dedicate all'interno del processo che li ha creati e non tramite
system call. II thread a livello utente sono gestiti dagli utenti e il kernel non ne è a conoscenza, il che comporta:

Vantaggi:
Esempi:
● Questi thread sono più veloci da creare e gestire
● Il kernel li gestisce come se fosse un processo a thread singolo ● Ambiente UNIX
● Efficienza di gestione (tempi di switching ridotti)
● Flessibilità e scalabilità
● Portabilità

Svantaggi:

● Sospensione del processo se un thread effettua una system call


● Impossibilità di sfruttare il parallelismo fisico su architetture multiprocessore
I thread o "processi leggeri"
Multithreading a livello kernel (Kernel-Level)

In questa modalità, la gestione dei thread è affidata al kernel del sistema operativo. I thread sono schedulati, sospesi e
risvegliati dal kernel come se fossero processi.

Vantaggi:

● Evoluzione di un secondo thread se un thread si sospende


● Sfruttamento del parallelismo fisico su architetture multiprocessore
● Possibilità di implementare il kernel come sistema multithread

Svantaggi:

● Efficienza ridotta a causa dei tempi di cambio di contesto del kernel. La schedulazione risulta essere più complessa
in quanto deve gestire la copresenza di processi e thread.

Esempi:

● Linux
● Windows
I thread o "processi leggeri"
SOLUZIONE MISTA

Esistono anche soluzioni miste, come quella implementata in Solaris, che combinano le caratteristiche del Kernel-Level e
del User-Level. I thread utente vengono "mappati" su thread a livello kernel, sfruttando i vantaggi di entrambi i modelli.

Vantaggi della soluzione mista:

● Esecuzione parallela di thread della stessa applicazione su processori diversi


● Riduzione dell'impatto sul processo in caso di chiamate al kernel da parte di un thread

La scelta della modalità di implementazione dipende da diverse variabili, tra cui le caratteristiche del sistema operativo,
l'architettura hardware e le specifiche esigenze dell'applicazione.
I thread o "processi leggeri"
Thread POSIX

Il modello ANSI/IEEE per i thread è definito dallo standard POSIX (Portable Operating System Interface for Computing
Environments), che comprende un insieme di direttive per le interfacce applicative (API) dei sistemi operativi.

Obiettivo dello standard:

● Indicare le regole da rispettare per realizzare strumenti compatibili o programmi applicativi portatili in ambienti
diversi.
● Un programma che utilizza solo i servizi previsti dalle API di POSIX può essere portato su tutti i sistemi operativi che
le implementano.

Thread POSIX e libreria pthreads.h:

● I thread POSIX sono chiamati Pthread.


● Vengono utilizzati per la realizzazione di programmi concorrenti richiamando la libreria <pthread.h>.
● La libreria definisce il tipo pthread_t che viene usato per definire i thread nei programmi concorrenti creandoli
all'interno di un processo (quarta situazione: thread multipli per processo).
I thread o "processi leggeri"
Inclusione della libreria:

● In alcuni compilatori, la libreria base del linguaggio C include già le funzionalità per la gestione delle espressioni
regolari definite dallo standard POSIX.
● In tali casi, non esiste una libreria C separata da quella POSIX.
● A seconda del compilatore, è necessario verificare quando è necessario specificare l'inclusione della libreria
precompilata per la gestione di una specifica funzionalità POSIX durante la compilazione.

Stati di un thread

Come per i processi, anche i thread passano attraverso diversi stati durante il loro ciclo di vita.

Ciclo di vita di un thread:

● Legato alla vita del processo che lo genera: La terminazione di un processo comporta la terminazione di tutti i
suoi thread.
● "Indipendente" durante la vita: Un thread è attivo all'interno del processo, ma evolve indipendentemente dal fatto
che il processo sia in esecuzione o in attesa.
I thread o "processi leggeri"
Diagramma degli stati:

Il diagramma mostra gli stati di un thread:

● Idle: Prima di essere avviato.


● Ready: Pronto per l'esecuzione ma in attesa della CPU.
● Running: In esecuzione.
● Blocked: In attesa di completare l'I/O.
● Dead: Terminate le sue istruzioni.
● Waiting: In attesa di un evento.
● Sleeping: Sospeso per un periodo.

Le transizioni:

● Le transizioni in rosso sono sotto il controllo del sistema.


● Le transizioni in verde sono effettuate da istruzioni, dove il controllo è del programma.
I thread o "processi leggeri"

Descrizione degli stati:

● Idle: Un thread è Idle quando non è ancora avviato.


● Ready: Un thread viene posto nello stato di Ready dove condivide una coda con tutti i thread e i processi che
attendono la CPU. La gestione avviene tramite diverse politiche di scheduling, alternando Running a Ready.
● Running State: In esecuzione. Può essere sospeso per tre cause:
○ Blocked State: Per operazioni di I/O. Passa allo stato di Waiting quando esegue chiamate bloccanti.
○ Sleeping State: Effettua cicli di attesa per un certo tempo, poi ritorna nello stato di Ready.
○ Dead State: Il thread ha completato tutte le istruzioni.
* Foreground → si riferisce a un'applicazione o a un processo
I thread o "processi leggeri" che sta attualmente ricevendo l'attenzione diretta dell'utente

Comportamento in caso di blocco per I/O:

● Thread Java a livello utente: Il sistema non vede gli altri thread dello stesso processo ed esegue un processo
differente.
● Thread C a livello kernel: Il sistema può gestire lo scheduling a livello thread secondo una politica definita nel
sistema operativo.

Utilizzo dei thread

Una delle principali applicazioni dei thread è quella di permettere l'esecuzione di lavori con attività in *foreground e
in background:

● Esempio: Mentre un thread gestisce l'I/O con l'utente, altri thread eseguono operazioni sequenziali di calcolo in
background.
● Altri esempi:
○ Fogli elettronici: procedure di ricalcolo automatico.
○ Word processor: reimpaginazione o controllo ortografico del documento.
○ Web: ricerche nei motori o nei database.

Un altro importante utilizzo dei thread è quello di implementarli per l’esecuzione di attività asincrone, come per esempio le operazioni di
garbage collection nella gestione della RAM oppure nelle procedure di salvataggio automatico dei dati (backup schedulato).
Elaborazione concorrente - L4
TPSI, 4BP
Elaborazione concorrente
Elaborazione concorrente
Generalità sulla Programmazione Sequenziale

● La programmazione imperativa si basa sull'esecuzione sequenziale, dove le azioni vengono eseguite una dopo l'altra.
● L'elaborazione sequenziale produce un processo sequenziale con un ordine totale delle azioni.
● Il teorema di Böhm-Jacopini individua la sequenza come una delle tre strutture fondamentali.
● Gli algoritmi imperativi sono composti da una sequenza finita di istruzioni, in corrispondenza delle quali, durante la
loro esecuzione, l’elaboratore passa attraverso una sequenza di stati predefinita.

Elaborazione Sequenziale e Architettura di Von Neumann

● Il modello di Von Neumann è basato su una singola CPU che esegue istruzioni sequenziali.
● Alcune attività possono essere parallelizzate, ad esempio, le fasi di input lunghe possono essere ridotte con la
parallelizzazione.
● Applicazioni come i server Web, i videogiochi multi-giocatore e i robot richiedono attività parallele che la
programmazione sequenziale NON può affrontare efficacemente.

Le applicazioni concorrenti possono essere progettate per eseguire diverse attività in parallelo, sfruttando al massimo la capacità di elaborazione della
CPU. Tuttavia, anche su un sistema con una singola CPU, la programmazione concorrente può ancora essere utile per migliorare l'efficienza, ad
esempio eseguendo operazioni di I/O in parallelo con il calcolo.
Elaborazione concorrente
Programmazione Concorrente

● La programmazione concorrente gestisce più attività eseguite contemporaneamente.


● È realizzabile con architetture multiprocessore, dove più processori eseguono programmi separati.
● Il sistema operativo è un esempio di programmazione concorrente, gestendo l'allocazione delle risorse hardware per
massimizzare l'efficienza.

● Definizione di sistema concorrente: software che gestisce attività correlate o competitive per risorse condivise.
● Definizione di processo concorrente: due processi sono concorrenti se l'inizio di uno avviene prima del completamento
dell'altro.
Elaborazione concorrente
Processi non sequenziali e Grafo di Precedenza

● Nei processi sequenziali, l'ordine degli eventi è completamente determinato e può essere rappresentato da un grafo
orientato, che è una lista ordinata degli eventi.
● Un grafo che rappresenta l'ordine temporale delle azioni è chiamato Grafo delle Precedenze.

● Nei processi paralleli, l'ordine non è completamente definito, e l'esecutore può scegliere liberamente quale
istruzione avviare prima senza compromettere il risultato.
● Nel contesto dell'elaborazione parallela, l'ordine delle istruzioni segue un ordinamento parziale.
● Il Grafo delle Precedenze (o Diagramma delle Precedenze) è utilizzato per descrivere questa libertà
nell'evoluzione dei processi concorrenti.
● In un processo sequenziale, il Grafo delle Precedenze si riduce a una lista ordinata (rif. immagine sopra)
● In un processo parallelo, il Grafo delle Precedenze è un grafo orientato aciclico, con percorsi alternativi che
indicano la possibilità di esecuzione contemporanea di più istruzioni.
Elaborazione concorrente
- Per descrivere il Grafo delle Precedenze, vengono utilizzati i seguenti simboli con i seguenti significati:

I grafi di precedenza, sono schemi in cui si


rappresenta l’evoluzione di un processo. Sono
formati da:
1. nodi del grafo rappresentano i singoli eventi
2. archi del grafo rappresentano le
precedenze temporali

PROCESSO SEQUENZIALE
- Grafo a ordinamento totale
- Ogni nodo ha un predecessore (tranne il nodo iniziale) e un successore (tranne il nodo finale)

PROCESSO PARALLELO
- Grafo a ordinamento parziale
- Consente l'esecuzione di più processi contemporaneamente
Elaborazione concorrente
In sintesi:

- alcuni problemi possono essere risolti mediante processi di calcolo non sequenziali cioè rappresentati da
un grafo ad ordinamento parziale
- in questo caso il problema può essere risolto da alcuni moduli sequenziali che lavorano in parallelo.
- Occorre quindi un elaboratore parallelo, in grado cioè di eseguire un numero arbitrario di operazioni
contemporaneamente.
- Occorre quindi un linguaggio di programmazione con il quale poter descrivere questi algoritmi non
sequenziali.
- Lo studio di questi linguaggi, dei loro compilatori e delle loro applicazioni prende il nome di
Programmazione Concorrente.
Elaborazione concorrente
Elaborazione concorrente
Elaborazione concorrente
Elaborazione concorrente
Elaborazione concorrente
Scomposizione di un Processo non Sequenziale

● Un processo non sequenziale (parallelo) coinvolge l'elaborazione contemporanea di più processi, ciascuno dei
quali è di tipo sequenziale.
● Questi processi possono essere scomposti in un insieme di processi sequenziali che possono essere eseguiti
contemporaneamente.
● Lo studio dei processi paralleli può essere affrontato scomponendoli in processi sequenziali e risolvendoli
singolarmente tramite la programmazione classica.
● Per poter correttamente descrivere la concorrenza è necessario distinguere le attività che i processi eseguono in due
tipologie:
a. attività completamente indipendenti;
b. attività interagenti

Processi Indipendenti

● I processi completamente indipendenti non generano situazioni problematiche e possono essere eseguiti in
sequenza o in parallelo.
● L’unica accortezza è quella di rispettare per ogni processo l’ordine stabilito delle operazioni.
Elaborazione concorrente
Elaborazione concorrente
Processi Interagenti

● I processi che devono interagire possono essere classificati in base alla loro conoscenza reciproca.
a. Processi totalmente ignari competono per le risorse comuni e richiedono sincronizzazione da parte del SO
(funge da arbitro).

b. Processi indirettamente a conoscenza uno dell'altro possono cooperare utilizzando risorse condivise.
c. Processi direttamente a conoscenza uno dell'altro possono comunicare direttamente scambiandosi
messaggi espliciti.
Elaborazione concorrente
DEF.

● Mutua Esclusione: Processi non possono


accedere contemporaneamente a risorse
condivise.
● Deadlock: Processi si bloccano reciprocamente
aspettando risorse detenute da altri.
● Starvation: Processo non ottiene mai accesso a
una risorsa desiderata a causa di priorità più
basse rispetto ad altri processi.
Elaborazione concorrente
Meccanismi di Comunicazione e Sincronizzazione

1. Cooperazione tra Processi: In alcune situazioni, i processi devono collaborare, richiedendo una sincronizzazione
accurata per evitare conflitti e garantire un'esecuzione ordinata delle operazioni.

2. Errori Dipendenti dal Tempo: La mancanza di sincronizzazione può causare errori dipendenti dal tempo, difficili da
individuare poiché legati alla velocità e all'evoluzione autonoma dei singoli processi. Questi errori potrebbero non
ripetersi anche in condizioni identiche.
Elaborazione concorrente
Tecniche di Sincronizzazione
Le tecniche di sincronizzazione possono essere implementate in due modi principali:

1. Memoria Condivisa (Shared Memory):


● Produttore/Consumatore: Un processo (produttore) scrive dati in una memoria condivisa (buffer) mentre un
altro processo (consumatore) legge e utilizza questi dati.

2. Scambio di Messaggi:
● Inter Process Communication (IPC): I processi comunicano scambiandosi messaggi, con meccanismi simili a
operazioni di input/output.
Elaborazione concorrente
Comunicazione Diretta
La comunicazione diretta avviene attraverso due primitive:

● send(processo_destinatario, messaggio): Per inviare un messaggio a un altro processo.


● receive(processo_mittente, messaggio): Per ricevere un messaggio da un processo.
La descrizione della concorrenza - L5
TPSI, 4BP
La descrizione della concorrenza
La descrizione della concorrenza
1. Elaboratore non sequenziale:
● Architettura parallela:
● Utilizza sistemi multiprocessori/multielaboratori.
● Consente l'esecuzione simultanea di più operazioni.
● Ogni processore ha la sua unità di elaborazione e memoria.
● Es: Supercomputer, cluster di calcolo ad alte prestazioni.

● Sistemi monoprocessori multiprogrammati:


● Sfruttano il parallelismo virtuale su un singolo processore.
● Il sistema operativo gestisce l'esecuzione concorrente di più processi.
● I processi vengono eseguiti alternativamente e in modo trasparente all'utente.
● Es: Sistemi desktop, server.

Nei nostri esempi tratteremo sempre il caso del parallelismo virtuale in modo da poter scrivere e collaudare i
programmi sui nostri PC che hanno tutti un sistema operativo multiprogramma.
La descrizione della concorrenza
2. Linguaggi non sequenziali:
● Linguaggi di programmazione concorrente:
● Forniscono costrutti specifici per gestire la concorrenza.
● Consentono la descrizione di attività parallele.
● Alcuni linguaggi offrono costrutti come fork-join e cobegin-coend.
● Scomposizione sequenziale:
● Processo non sequenziale viene suddiviso in processi sequenziali.
● Identificazione di attività che possono essere eseguite simultaneamente.
● Descrizione delle attività come sequenze di istruzioni (blocchi sequenziali).
La descrizione della concorrenza
3. Algoritmi paralleli:
● Utilizzo di linguaggi concorrenti:
● Programmazione di algoritmi che sfruttano l'esecuzione parallela.
● Implementazione di costrutti specifici per gestire i processi paralleli.

● Costrutti specifici:
● fork-join: Per creare e terminare processi sequenziali.
● cobegin-coend: Per avviare e terminare processi concorrenti.

4. Benefici dell'esecuzione parallela:


● Miglioramento delle prestazioni:
● Riduzione dei tempi di esecuzione.
● Sfruttamento più efficiente delle risorse hardware disponibili.
● Scalabilità:
● Possibilità di aumentare le prestazioni aggiungendo più risorse di calcolo.
● Gestione di compiti complessi:
● Scomposizione di problemi complessi in sotto-problemi più gestibili.
La descrizione della concorrenza
Fork-join

Le istruzioni fork e join furono introdotte nel 1963 da Dennis e Van Horn per descrivere l’esecuzione parallela di
segmenti di codice mediante la “scomposizione” di un processo in due processi e la successiva “riunione” in un
unico processo.

Fork nel Grafo delle Precedenze:


1. Definizione:
● La fork rappresenta la biforcazione di un nodo in due rami nel grafo delle precedenze.
2. Esecuzione:
● L'esecuzione di una fork corrisponde alla creazione di un nuovo processo.
● Il nuovo processo inizia la sua esecuzione in parallelo con quella del processo chiamante.
3. Implementazione in Pseudocodice:

come si individua una


FORK?
→ diramazione
La descrizione della concorrenza
Join nel Grafo delle Precedenze:
1. Definizione:
● La join è l'istruzione eseguita quando il processo creato tramite fork ha completato il suo compito.
● Si sincronizza con il processo genitore e termina la sua esecuzione.
2. Esecuzione:
● Il processo genitore attende il completamento del processo figlio tramite la join.
● Una volta che il processo figlio ha terminato, la join sincronizza e termina la sua esecuzione.
3. Implementazione in Pseudocodice:

come si individua una


JOIN?
Se su di un nuovo ci sono più
archi in entrata, allora
significa che su quel nodo c'è
una JOIN.
La descrizione della concorrenza
Join nel Grafo delle Precedenze:
1. Implementazione in Pseudocodice:
La descrizione della concorrenza
Join nel Grafo delle Precedenze:
Se ipotizziamo che ogni nodo sia una routine o un qualunque sottoprogramma
che svolge una singola attività, possiamo meglio comprendere il
funzionamento dell’istruzione fork con il seguente schema, dove sono messi in
evidenza i due processi:

- P1 esegue la Routine A;
- con la fork attiva il Processo 2 e in parallelo si eseguono la Routine B e la
Routine C;
- P1 attende con la join la terminazione di P2;
- P1 esegue la Routine C

Nella definizione appena descritta la fork restituisce un identificatore di


processo, che viene successivamente accettato da una join, in modo che sia
specificato con quale processo intende sincronizzarsi.
Lo svantaggio di questa definizione è che la join può ricongiungere solo due
flussi di controllo
La descrizione della concorrenza
Join (count)

Esiste anche una formulazione estesa


dell’istruzione join:

join(count);

dove count è una variabile intera non negativa e


indica il numero di processi che si “riuniscono”
in quel punto: viene utilizzata nel caso di
terminazione congiunta di un numero
superiore a due processi, come nel seguente
grafo delle precedenze che viene codificato con
il segmento di programma riportato a lato.
La descrizione della concorrenza

ll linguaggio di shell UNIX/Linux ha due system call simili ai costrutti di alto livello definiti da Dennis e Van Horn:
- fork(): è l’istruzione utilizzata per creare un nuovo processo;
- wait(): corrisponde all’istruzione join e viene utilizzata per consentire a un processo di attendere la terminazione
di un processo figlio

Sono però concettualmente diverse dato che sono chiamate di sistema mentre quelle da noi descritte sono istru-zioni
di un ipotetico linguaggio concorrente di alto livello: possono comunque essere utilizzate didatticamente per realizzare
programmi concorrenti
La descrizione della concorrenza
La descrizione della concorrenza
Cobegin-coend

Il secondo costrutto che utilizziamo per descrivere la concorrenza è il cobegin-coend: è un costrutto che permette
di indicare il punto in cui N processi iniziano contemporaneamente l’esecuzione (cobegin) e il punto in cui la
terminano, confluendo nel processo principale (coend).La sintassi del comando è:

cobegin
<elenco delle attività parallele> Al termine dell’esecuzione della
coend sequenza S1 vengono attivati tre
processi che eseguono in parallelo le
sequenze di istruzioni S2, S3, S4: il
processo padre S1 si sospende e
rimane in attesa della loro
terminazione.All’interno dei singoli
processi possono essere presenti
delle istruzioni di cobegin coend,
cioè è possibile avere l’annidamento
di più costrutti cobegin-coen
La descrizione della concorrenza
Cobegin-coend
La descrizione della concorrenza
Cammino Parallelo:
1. Definizione:
● Un cammino parallelo è una sequenza di nodi delimitata da un nodo cobegin e un nodo coend.
2. Esempio:
● Nell'esempio fornito, sono presenti due cammini paralleli: uno esterno e uno annidato all'interno del
primo.

Equivalenza di Fork-Join a Cobegin-Coend:


1. Facilità d'Uso:
● Il costrutto cobegin-coend è più semplice da utilizzare rispetto a fork-join.
● Il codice risultante è più strutturato e comprensibile.
2. Spaghetti Code:
● Fork-join può generare codice più complesso e meno strutturato, simile a "spaghetti code"
● L'uso eccessivo di istruzioni di salto incondizionato come "gotolabel" è controindicato nella
programmazione strutturata.
La descrizione della concorrenza
Equivalenza di fork-join a cobegin-coend
1. Sostituzione:
● Fork-join può essere sostituito da cobegin-coend se non ci sono strutture annidate.
● Nel caso di terminazione congiunta di tutti i processi, l'equivalenza è possibile.

2. Limitazioni di Cobegin-Coend:
● Non tutti i programmi paralleli possono essere descritti utilizzando cobegin-coend.
● Tutti i programmi paralleli possono essere descritti utilizzando il costrutto fork-join.
● Cobegin-coend può essere limitato nella sua capacità di descrivere strutture parallele complesse.
La descrizione della concorrenza

È sempre possibile trasformare un programma parallelo descritto con istruzioni cobegin-coend mediante istruzioni fork-join
La descrizione della concorrenza

NoN sempre è possibile trasformare un programma parallelo descritto con istruzioni fork-join utilizzando
istruzioni cobegin-coend: lo si può fare solamente se il grafo è strutturato.

Grafo strutturato
Un grafo per poter essere espresso soltanto con cobegin e coend deve essere tale che detti X e Y due nodi
del grafo, tutti i cammini paralleli che iniziano da X terminano con Y e tutti quelli che terminano con Y iniziano
con X o, più sinteticamente, ogni sottografo deve essere del tipo one-in/one-out. In questo caso il grafo si
dice strutturato.
La descrizione della concorrenza

La codifica con il costrutto fork-join è invece fattibile, ma è necessario introdurre le istruzioni di salto in
quanto le fork si intersecano tra loro:

La join etichettata con L1


non avviene tra gli stessi due
processi che hanno fatto la
prima fork, ma tra il primo
processo e un processo
generato dalla join tra due
figli: il processo che esegue E
e quello che esegue C
La descrizione della concorrenza
Semplificazione delle Precedenze:
1. Analisi del Grafo delle Precedenze:
● Prima di scrivere un programma parallelo, è importante analizzare il grafo delle precedenze.
● Scopo: semplificare il grafo eliminando le precedenze implicite.
2. Precedenze Implicite:
● Alcune relazioni di dipendenza tra le operazioni possono essere implicite e non esplicite nel grafo.
3. Eliminazione delle Precedenze Ridondanti:
● Quando sono presenti precedenze implicite, è possibile rimuovere gli archi ridondanti per semplificare
il grafo.
● Ciò riduce la complessità e rende il grafo più chiaro e comprensibile.
La descrizione della concorrenza
La descrizione della concorrenza

l grafo di partenza può essere trasformato in


quest’ultimo, dove si vede che l’unica
operazione che può essere parallelizzata è
quella dei nodi D ed E.

Parallelizzare il primo grafo non porterebbe


nessun vantaggio in termini di tempo di
elaborazione.

Potrebbero piacerti anche