Gestione processi in Python con il modulo multiprocessing
- Admin
- Didattica , Python
- 10 Sep, 2022
Python ci mette a disposizione un modulo, multiprocessing
, che semplifica notevolmente la gestione dei processi, nascondendo all’ utente i dettagli tecnici e fornendo una soluzione multipiattaforma alla gestione degli stessi.
Per crare un nuovo processo in python sarà sufficiente definire la funzione che questo processo dovrà eseguire e passarla come parametro al costrutture della classe Process. Questa funzione sarà il task che viene eseguito della processo child. L’oggetto creato potra poi essere utilizzato per gestire il processo (del sistema operativo).
from multiprocessing import Process
def f():
print("ciao dal processo child")
p = Process(target=f)
p.start()
p.join()
Essendo il modulo multiprocessing
un modulo portabile, ogni sistema operativo metterà a disposizione i propri meccanismi di creazione di un nunovo processo.
comunicazione tra processi
Come abbiamo accennato poco prima, la comunicazione tra i processi è una questione complessa. Abbiamo detto che per consentire la comunicazione tra due processi, e quindi il passaggio di informazioni tra spazi di indirizzamento isolati tra di loro, sarà necessario chiedere al sistema operativo di mettere a disposizione una zona di memoria condivisa dove i due processi andranno a scrivere e leggere i messaggi che si andranno a scambiare. L’ accesso a questa zona dovrà essere protetto da accessi concorrenti attraverso tecniche di sincronizzazione.
Il modulo multiprocessing
ci mette a disposizione un oggetto, Queue
, che rappresenta una coda sincronizzata di messaggi. Chi possiede un riferimento alla coda può:
- Attendere che sia presente un messaggio nella coda e ritornarlo (
Queue.get()
),. nota: questo metodo è bloccante e non si prosegue l’esecuzione fino a che non arriva un messaggio da leggere. - Inserire un messaggio nella coda (
Queue.put(msg)
) dovemsg
è il messaggio (stringa, oggetto, etc…) da inserire nella coda. - Controllare se la coda è vuota (
Queue.empty()
)
Il processo parent, per poter comunicare con un processo child, dovrà quindi condividere con il processo che crea un oggetto di tipo Queue
. A quel punto entrambi potranno inserire messaggi nella coda e attendere la ricezione d i un messaggio con get()
. A prima vista potrebbe sembrare critico il fatto che get()
è bloccante, tuttavia coì non è se pensiamo che è sempre possibile, detta q
la coda, possiamo prima testare se vi sono messaggi in q
e solo in quel caso chiamare la get()
che bloccherà solo per il tempo di lettura del dato dalla coda (è bloccante per il fatto che la coda viene sincronizzata).
if not q.empty(): # se la coda non è vuota
msg = q.get() # recupera il messaggio
comunicazione bidirezionale
Impostare una comunicazione bidirezionale tra due processi è una operazione che deve essere fatta con attenzione. Le code sono un sistema di comunicazione monodirezionale, per impostare una comunicazione bidirezionale tra il parent e un processo child p0
che avrà come target la funzione child_task
dovremo creare due code e passarle al processo in modo invertito. La coda su cui il processo parent va a scrivere sarà quella su cui il child si mette in ascolto, viceversa quella su cui risponderà il child sarà quella di ascolto per il parent.
from multiprocessing import Process, Queue
from time import sleep
def child_task(rx_queue,tx_queue):
while True:
if not rx_queue.empty():
msg = rx_queue.get()
print(f"ricevuto {msg}")
sleep(1)
tx_queue.put("quit")
print("done")
return
tx = Queue()
rx = Queue()
p0 = Process(target=child_task,args=(tx,rx))
p0.start()
sent = False
while True:
if rx.empty() and not sent:
cmd = input(">")
tx.put(cmd)
sent = True
else:
msg = rx.get()
print(f"ricevuto dal parent: {msg}")
if msg== "quit":
break
else:
sent = False
p0.join()
in questo caso lavariabile sent
consente di dire che è già stato inviato un messaggio e si sta aspettando di ricevere una risposta. La risposta sarà sempre la stringa "quit"
che provocherà il termine del programma, data la condizione
if msg== "quit":
break