Python: gestire i processi con la fork

La gestione dei processi é una delle funzioni più importanti del sistema operativo. Attraverso alcune system call é possibile per i processi in spazio utente di accedere a funzionalitá del sistema operativo. Ad esempio la creazione di un nuovo processo viene effettuata attraverso la funzione fork() che crea un nuovo processo, copiando tutte le informazioni del processo parent.

Il nuovo processo cosí realizzato avrá le stesse informazioni del processo parent, ma con una differenza: il processo creato, detto anche processo child avrá un PID diverso ma memoria contenente una copia della memoria del parental momentoi della chiamata alla funzione fork().

Approfondire

puoi trova più informazioni sulla funzione fork() qui: man page fork(2)

per chiamare la fork dobbiamo per prima cosa importare il modulo os che contiene numerose funzioni e classi per accedere in modo portabile a funzionalitá che dipendono dal sistema operativo.

Il nostro punto di partenza sará il seguente codice:

import os
print("1")
os.fork()
print("2")

Eseguendo questo codice vedremo stampati a video i seguenti messaggi:

1
2
2

Per quale motivo? 1 viene stampato da processo parent mentre 2, essendo eseguito dopo la fork(), viene stampato sia dal processo parent che dal processo child. Questo avviene in quanto il processo child è un processo figlio del processo parent ed entrambi condividono il terminale dove il processo é stato originariamente lanciato.

Per prima cosa é necessario sapere che la fork ci consente di capire se siamo nel processo parent o nel processo child. infatti la chiamata alla fork() restituisce un intero che se è uguale a 0 allora siamo nel processo child, se é un numero positivo siamo nel processo parent, altrimenti si é verificato un errore.

Se quindi assegnamo il valore restituito dalla fork() alla variabile pid vedremo che il processo child avrá un PID diverso dal processo parent (il processo child avrá una memoria diversa dal processo parent anche se il codice che esegue sará lo stesso.

import os
pid = os.fork()
if pid == 0:
print("sono il processo child")
else:
print("sono il processo parent")

Per verificare il pid di un processo dobbiamo eseguire la seguente chiamata: os.getpid(). Quindi modifichiamo il codice appena scritto per stampare anche il pid di chi effettua la stampa:

import os
pid = os.fork()
if pid == 0:
print(f"sono il processo child con pid = {os.getpid()}")
else:
print(f"sono il processo parent con pid = {os.getpid()}")

Il compito del processo parent sarà solitamente quello di attendere che il processo child vada a terminare. Questa attesa verrà effettuata con la chiamata alla funzione os.waitpid(). Una volta terminato il processo child, il parent proseguirà l’esecuzione.

import os

print("prime della fork")
pid = os.fork()

if pid > 0:
print("dopo la fork parent")
os.waitpid(pid,0)
elif pid == 0:
print("dopo la fork nel processo figlio")
else:
print("errore")

Una delle caratteristiche dei processi è che non abbiamo possibilità di far comunicare direttamente i processi parent e child. L’unico modo per farli comunicare sarebbe quello di utilizzare una sezione di memoria condivisa, sui cui andrebbero sincronizzati accessi e letture, in modo tale da rendere impossibili scritture concorrenti o letture durante una scrittura. Per queste operazioni utilizzeremo un approccio di più alto livello mediante la libreria multiprocessing di python. Limitiamoci a delineare uno schema dell’architettura che consente al processo parent e child di comunicare.

Trovi il codice completo su replit: https://replit.com/@professorandrea/GestioneProcessiFork

e il video relativo su Youtube:

Andrea Pollini

Matematico, informatico.