· Andrea Pollini · materiale didattico · 17 min read
Processi e thread
processi, thread e loro gestione in python.
I processi furono introdotti nei sistemi operativi non appena vi furono i miglioramenti hardware che consentirono di gestire in modo accettabile i processi, senza provocare un consumo percentuale troppo elevato della CPU.
Il sistema operativo sappiamo infatti che si occupa di gestire tutti i sottosistemi (processi, memoria, comunicazioni di rete e in generale con le periferiche di I/O) e questa gestione occupa una fetta dei cicli di CPU disponibili. Ogni evoluzione dell’hardware consente al sistema operativo di avere a disposizione una quantità di cicli di clock maggiore e questo viene speso in parte per avere più potenza di calcolo da dare agli utenti ed in parte per avere dei sistemi di gestione più complessi che portino ad una gestione più efficace.
[!NOTE] Se aumentano le istruzioni a disposizione per un secondo di CPU… Se la gestione dei processi dovesse costare 1000 cicli di clock ad esempio questo nel caso di un sistema dove vengono eseguiti 1k istruzioni al secondo ha un impatto dell’1%, se le istruzioni a disposizione diventassero 100k al secondo l’impatto diventerebbe solo dello 0.001%
I processi
I processi sono stati introdotti nei sistemi operativi per gestire in modo efficiente le risorse del computer e permettere l’esecuzione di più programmi contemporaneamente. Prima dell’introduzione dei processi, ogni programma doveva essere eseguito singolarmente e in sequenza, il che rendeva difficile utilizzare il computer per più compiti contemporaneamente.
L’introduzione dei processi ha permesso ai sistemi operativi di gestire più programmi contemporaneamente, assegnando loro risorse come la CPU e la memoria in modo equo. In questo modo, i processi possono essere eseguiti in parallelo o in serie, a seconda delle risorse disponibili e delle priorità dei processi.
Inoltre, l’uso dei processi ha reso possibile l’esecuzione di programmi più complessi, che richiedono la collaborazione di più sottoprocessi per completare un compito. Ad esempio, un programma di elaborazione di immagini potrebbe suddividere il lavoro in più sottoprocessi, ciascuno responsabile dell’elaborazione di una porzione dell’immagine.
Ogni processo ha il proprio spazio di indirizzamento della memoria, risorse aperte e stato di esecuzione. I processi possono essere creati, sospesi, ripresi ed eliminati durante l’esecuzione del sistema operativo.
Una possibilità interessante quando si utilizzano i processi è quella di sfruttare tecniche di gestione di più processi contemporaneamente, quali multiprogrammazione e multitasking.
multiprogrammazione
La multiprogrammazione è una tecnica utilizzata dai sistemi operativi per gestire più processi contemporaneamente. In un sistema a multiprogrammazione, il sistema operativo tiene traccia di più processi in esecuzione e assegna loro risorse come la CPU e la memoria in modo equo. Ciò consente al sistema operativo di passare da un processo all’altro rapidamente, dando l’impressione che i processi vengano eseguiti contemporaneamente.
multitasking
Il multitasking è una caratteristica dei sistemi operativi che permette all’utente di eseguire più compiti contemporaneamente. Ad esempio, l’utente può avere in esecuzione un programma di elaborazione di testo mentre ascolta musica o scarica file da Internet. In realtà, il sistema operativo sta gestendo più processi contemporaneamente, ma li sta eseguendo uno alla volta sulla CPU. Grazie al multitasking, l’utente ha l’impressione che i compiti vengano eseguiti contemporaneamente.
Gli stati di un processo e la gestione con python
Lo stato di un processo rappresenta il suo status corrente all’interno del sistema operativo. I processi possono essere in uno dei seguenti stati:
- Nuovo: il processo è stato creato ma non è ancora pronto per l’esecuzione.
- Pronto: il processo è pronto per l’esecuzione sulla CPU e sta aspettando che il sistema operativo lo selezioni.
- In esecuzione: il processo sta utilizzando la CPU per eseguire il proprio codice.
- In attesa: il processo ha sospeso la propria esecuzione in attesa di un evento esterno, come l’apertura di un file o la ricezione di un segnale.
- Terminato: il processo ha completato la sua esecuzione e sta per essere eliminato dal sistema operativo.
In Python, è possibile utilizzare la libreria multiprocessing
per creare nuovi processi e gestire il loro stato. Ecco un esempio di come si può creare un nuovo processo e variarne lo stato:
In questo esempio, creiamo un nuovo processo utilizzando la classe Process
della libreria multiprocessing
. La funzione worker()
viene eseguita dal processo creato. Utilizziamo il metodo is_alive()
per verificare lo stato del processo e stampiamo un messaggio appropriato. Alla fine dell’esecuzione, il processo si termina e il suo stato diventa “terminato”.
I thread
I thread sono unità esecutive all’interno di un processo. In altre parole, un processo può contenere uno o più thread, che rappresentano sequenze indipendenti di istruzioni eseguibili contemporaneamente.
Abbiamo visto che un processo è rappresentato da un process control block (PCB) all’interno del sistema operativo. Allo stesso modo un thread verrà rappresentato ad un thread control block (TCB)
il thread control block e il process control block
Il Thread Control Block (TCB) e il Process Control Block (PCB) sono entrambi dati di struttura utilizzati dai sistemi operativi per gestire i thread e i processi rispettivamente. Il PCB contiene informazioni sullo stato del processo, come ad esempio l’ID del processo, le risorse allocate, lo stato del processo (ad esempio in esecuzione, sospeso, pronto), il programma di avvio e la tabella dei pagine della memoria.
Il TCB, invece, è una struttura dati simile al PCB ma specifica per i thread. Contiene informazioni sullo stato del thread, come ad esempio l’ID del thread, lo stato del thread (ad esempio in esecuzione, sospeso, pronto), il puntatore allo stack del thread e il contesto di esecuzione del thread.
In generale, il TCB è una sotto-struttura del PCB. Ciò significa che ogni processo ha un proprio PCB e tutti i thread all’interno del processo condividono lo stesso PCB ma hanno ciascuno il proprio TCB. In questo modo, il sistema operativo può gestire efficacemente le risorse tra i thread di uno stesso processo e allo stesso tempo tenere traccia dello stato di ogni singolo thread. Inoltre, il TCB contiene anche informazioni sulla priorità del thread, che viene utilizzata dal sistema operativo per decidere quale thread eseguire quando più thread sono pronti per l’esecuzione. La priorità dei thread può essere modificata dinamicamente durante l’esecuzione del programma, ad esempio in base al carico di lavoro o alle esigenze dell’applicazione.
![[Pasted image 20240811111605.png]]
Creare un thread in python
In Python, è possibile creare un thread utilizzando la classe threading.Thread
. Ecco un esempio di come si può creare un thread in Python:
In questo esempio, la funzione funzione_thread
viene eseguita in un thread separato quando si chiama il metodo start()
sulla instancia della classe Thread
. Il metodo join()
viene utilizzato per attendere che il thread abbia terminato l’esecuzione prima di continuare con il resto del programma.
Il process control block (PCB)
Il Process Control Block (PCB) è una struttura dati utilizzata dai sistemi operativi per tenere traccia delle informazioni relative ad ogni processo attivo nel sistema. Il PCB contiene informazioni sullo stato del processo, come l’ID del processo, le risorse allocate, lo stato del processo (ad esempio in esecuzione, sospeso, pronto), il programma di avvio e la tabella dei pagine della memoria.
Ecco un esempio di come potrebbe essere strutturato un PCB:
I prerequisiti per capire la struttura del PCB sono:
- Conoscenza delle basi dei sistemi operativi, in particolare della gestione dei processi e dei thread.
- Comprensione di come i sistemi operativi utilizzano le strutture dati per tenere traccia dello stato dei processi e delle risorse allocate.
- Conoscenza delle principali informazioni che devono essere tenute traccia per ogni processo attivo nel sistema, come l’ID del processo, lo stato del processo, la priorità del processo e le risorse allocate.
Il thread control block (TCB)
Il Thread Control Block (TCB) è una struttura dati utilizzata dai sistemi operativi per tenere traccia delle informazioni relative ad ogni thread attivo nel sistema. Il TCB contiene informazioni sullo stato del thread, come l’ID del thread, lo stato del thread (ad esempio in esecuzione, sospeso, pronto), il puntatore al stack del thread e il contesto di esecuzione del thread.
Ecco un esempio di come potrebbe essere strutturato un TCB:
I prerequisiti per capire la struttura del TCB sono:
Le principali informazioni che devono essere tenute traccia per ogni thread attivo nel sistema sono:
- ID del thread: un identificatore univoco assegnato al thread dal sistema operativo per distinguerlo dagli altri thread.
- Stato del thread: indica lo stato di esecuzione del thread, ad esempio se è in esecuzione, sospeso o pronto per l’esecuzione. Questa informazione viene utilizzata dal sistema operativo per decidere quale thread eseguire quando più thread sono pronti per l’esecuzione.
- Priorità del thread: indica la priorità relativa del thread rispetto agli altri thread nel sistema. La priorità viene utilizzata dal sistema operativo per decidere quale thread eseguire quando più thread sono pronti per l’esecuzione.
- Puntatore allo stack del thread: indica l’indirizzo di memoria dell’ultima istruzione eseguita dal thread. Questa informazione viene utilizzata dal sistema operativo per ripristinare lo stato del thread quando viene sospeso e ripreso in esecuzione.
Inoltre, il TCB può contenere altre informazioni utili come ad esempio il contesto di esecuzione del thread sulla CPU, che include le istruzioni della CPU e i registri del thread.
I sistemi operativi utilizzano anche altre strutture dati per tenere traccia delle risorse allocate ai thread. Ad esempio, possono utilizzare una tabella di allocazione della memoria per tenere traccia dei blocchi di memoria assegnati ai thread e una tabella di gestione dei file per tenere traccia dei file aperti dai thread.
In generale, i sistemi operativi utilizzano queste strutture dati per monitorare lo stato dei thread e delle risorse allocate in modo da poter gestire efficacemente l’esecuzione dei programmi e garantire che le risorse siano utilizzate in modo corretto ed efficiente.
comunicazione tra processi
La comunicazione tra processi è una funzionalità importante dei sistemi operativi che permette ai processi di scambiare informazioni e dati tra loro. Ci sono diverse tecniche per la comunicazione tra processi, come ad esempio le pipe, i socket e i file condivisi.
Le pipe
Le pipe (o tubi) sono una tecnica di comunicazione tra processi che permette ai processi di scambiare informazioni attraverso un canale unidirezionale. Una pipe consiste in un buffer di memoria condivisa che può contenere dati da inviare da un processo a un altro.
Le pipe possono essere utilizzate per la comunicazione tra due processi in questo modo:
- Il primo processo crea una pipe e ne restituisce un’estremità (chiamata “estremità di scrittura”) al secondo processo.
- Il secondo processo utilizza l’estremità di scrittura per scrivere i dati nella pipe.
- Il primo processo utilizza l’altra estremità della pipe (chiamata “estremità di lettura”) per leggere i dati scritti dal secondo processo.
In questo modo, il secondo processo può inviare informazioni al primo processo attraverso la pipe.
Le pipe sono utili in situazioni in cui due processi devono scambiare informazioni tra loro, ad esempio in un’applicazione client-server o in una pipeline di elaborazione dei dati. Tuttavia, le pipe hanno alcune limitazioni, come il fatto che possono essere utilizzate solo per la comunicazione da un processo all’altro e non viceversa, e che i dati scritti nella pipe devono essere letti nell’ordine in cui sono stati scritti.
I socket
I socket sono una tecnica di comunicazione tra processi che permette ai processi di scambiare informazioni attraverso un canale bidirezionale. Un socket è un punto di accesso a una rete, ovvero un endpoint di comunicazione tra due processi.
I socket possono essere utilizzati per la comunicazione tra due processi in questo modo:
- Il primo processo crea un socket e lo lega ad un indirizzo IP e una porta specifica.
- Il secondo processo si connette al socket del primo processo utilizzando l’indirizzo IP e la porta specifica.
- Una volta stabilita la connessione, i due processi possono scambiare informazioni attraverso il socket in modo bidirezionale.
In Python, il modulo socket
fornisce una varietà di funzioni per la creazione e la gestione dei socket. Ecco un esempio di come creare un socket server che accetta connessioni e invia messaggi ai client connessi:
Ecco invece un esempio di come creare un client che si connette al server e riceve il messaggio inviato dal server:
In questo esempio, il client si connette al server utilizzando l’indirizzo IP localhost
e la porta 12345
, riceve il messaggio inviato dal server e lo stampa a schermo.
Comunicazione con file condivisi
I file condivisi (o file shared memory) sono una tecnica di comunicazione tra processi che permette ai processi di scambiare informazioni attraverso un’area di memoria condivisa. In pratica, i processi possono accedere alla stessa porzione di memoria e leggere o scrivere dati in modo sincronizzato.
I file condivisi possono essere utilizzati per la comunicazione tra due processi in questo modo:
- Il primo processo crea un file condiviso e ne restituisce il descrittore.
- Il secondo processo apre il file condiviso utilizzando il descrittore fornito dal primo processo.
- Entrambi i processi possono ora leggere o scrivere dati nel file condiviso.
In Python, la libreria mmap
fornisce funzioni per l’accesso ai file condivisi. Ecco un esempio di come creare un server che scrive in un file condiviso e un client che legge da esso:
In questo esempio, il server crea un file condiviso di dimensione 1024 byte e scrive i dati “Hello client!” in esso. Il client apre lo stesso file condiviso in modalità di sola lettura e legge i dati scritti dal server. Entrambi i processi utilizzano la libreria mmap
per accedere al file condiviso.
Nota che in questo esempio il file condiviso viene creato e aperto con il nome “shared_file”. Assicurati di utilizzare lo stesso nome per entrambi i processi o di modificare il codice per adattarsi alle tue esigenze.
comunicazione mediante code di messaggi
comunicazione tra processi
In Python, il modulo multiprocessing
fornisce un’interfaccia per la creazione e la gestione dei processi e per la comunicazione tra di loro. Ad esempio, è possibile utilizzare una Queue per far comunicare due processi. Ecco un esempio:
In questo esempio, vengono creati due processi p1
e p2
che eseguono rispettivamente le funzioni fun1()
e fun2()
. La funzione fun1()
inserisce un elemento nella coda q
, mentre la funzione fun2()
stampa l’elemento presente in testa alla coda q
. In questo modo, i due processi comunicano attraverso la coda q
.
In generale, la comunicazione tra processi può essere utilizzata per implementare una varietà di applicazioni, come ad esempio il calcolo distribuito o la gestione di un sistema operativo in multitasking. Ad esempio, nel calcolo distribuito, i processi possono scambiare informazioni tra loro per suddividere il carico di lavoro e accelerare i tempi di esecuzione dell’applicazione. Nella gestione di un sistema operativo in multitasking, la comunicazione tra processi può essere utilizzata per coordinare l’esecuzione di più compiti contemporaneamente. La comunicazione tra processi è un concetto fondamentale nell’ambito della programmazione parallela e del multiprocessing. Le diverse modalità di comunicazione tra processi, verranno poi scelte per essere utilizzate a seconda delle esigenze dell’applicazione.
comunicazione tra thread
Le code di messaggi sono una modalità di implementazione della concorrenza che consente ai thread di comunicare asincronamente attraverso una coda condivisa. In una coda di messaggi, i processi possono inviare messaggi alla coda e altri processi possono ricevere messaggi dalla coda. Ciò consente una comunicazione flessibile tra i processi senza la necessità di sincronizzazione esplicita.
In Python, la libreria queue
fornisce un’implementazione delle code di messaggi. Ecco un esempio di come utilizzare una coda di messaggi in Python:
In questo esempio, il thread producer
invia cinque messaggi alla coda condivisa q
. Il thread consumer
riceve i messaggi dalla coda e li stampa. Quando il produttore ha finito di inviare messaggi, il thread principale attende che il produttore finisca (t1.join()
) e quindi interrompe il consumatore inviando un messaggio None
alla coda.
Le code di messaggi sono utili in situazioni in cui i processi o i thread devono comunicare asincronamente o se si prevede una concorrenza interna alla coda. Tuttavia, possono essere più complesse da implementare rispetto ad altre modalità di implementazione della concorrenza come i thread o i processi.
Scelta del meccanismo di comunicazione tra processi
Ecco uno schema generale dei criteri di scelta delle metodologie di comunicazione tra processi:
- Tipo di informazione da scambiare: alcune informazioni possono essere scambiate più facilmente attraverso una modalità di comunicazione rispetto ad altre. Si pensi ad un testo piuttosto che una immagine.
- Numero di processi coinvolti: alcune modalità di comunicazione sono più adatte per un numero maggiore o minore di processi coinvolti.
- Necessità di sincronizzazione: alcune modalità di comunicazione richiedono una sincronizzazione più stretta tra i processi rispetto ad altre.
- Velocità di trasmissione: alcune modalità di comunicazione sono più veloci di altre per la trasmissione delle informazioni.
Metodologia | Tipo di informazione | Numero di processi | Necessità di sincronizzazione | Velocità di trasmissione |
---|---|---|---|---|
Coda di messaggi (FIFO) | semplice | Alto | Bassa | Alta |
Variabili condivise | Media | Basso | Alta | Bassa |
Socket | Complessa | Basso | Bassa | Alta |
Ecco un algoritmo in pseudocodice che guidi nella scelta della metodologia di comunicazione tra processi:
Nota che questo algoritmo è solo un esempio di come si potrebbe scegliere la metodologia di comunicazione tra processi in base ai criteri elencati. Potrebbero esserci altri fattori da considerare a seconda delle esigenze dell’applicazione.