Programmazione Concorrente - Teoria
Programmazione Concorrente - Teoria
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
- 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:
● 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.
● 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.
● Ciclo di vita: dal suo inizio alla terminazione, un processo passa attraverso diverse fasi:
● 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.
● 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.
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
La chiamata a sistema fork() permette di creare un processo duplicato del processo genitore. La fork() crea un
nuovo processo che:
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.
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:
Processi "zombie":
Se un processo figlio termina prima che il padre invochi wait(), il figlio diventa "defunct" o "zombie":
La funzione
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
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.
● 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
● 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
● 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).
● 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
● 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
● 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 leggeri:
● 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:
● 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.
● È 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.
● 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:
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.
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
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).
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:
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:
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.
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.
● 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.
● 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.
● 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:
Le transizioni:
● 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.
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.
● 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
● 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:
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.
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:
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:
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.
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.
- 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
join(count);
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.
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: