En Python, le multithreading n'exploite pas pleinement les cœurs de processeur. Pour une utilisation optimale des ressources multi-cœurs, la programmation multiprocessing est souvent nécessaire.
Le module multiprocessing simplifie grandement la création de processus parallèles. Il gère automatiquement les sous-processus, la communication, le partage de données et la synchronisation, avec des composants tels que Process, Queue, Pipe et Lock.
Classe Process
Syntaxe : Process([group[, target[, name[, args[, kwargs]]]]])
Paramètres : target désigne la fonction à exécuter, args les arguments positionnels sous forme de tuple, kwargs les arguments sous forme de dictionnaire. name est un alias, et group est raremnet utilisé.
Méthodes : is_alive(), join(timeout), run(), start(), terminate().
Attributs : authkey, daemon (doit être défini via start()), exitcode (None pendant l'exécution, -N si terminé par le signal N), name, pid. L'attribut daemon assure la terminaison automatique du processus enfant lorsque le processus parent se termine, et doit être configuré avant start().
1. Créer un processus unique
from multiprocessing import Process
def executer_tache(nom):
print(f"{nom} a été une bonne personne")
if __name__ == "__main__":
proc = Process(target=executer_tache, args=('alice',))
proc.start() # Démarre le processus
2. Lancer plusieurs prcoessus
from multiprocessing import Process
import time
import random
def activite_sportive(nom):
print(f"{nom} aime le sport")
time.sleep(random.randint(1, 3))
def activite_jeux(nom):
print(f"{nom} aime les jeux vidéo")
time.sleep(random.randint(1, 3))
if __name__ == "__main__":
p1 = Process(target=activite_sportive, args=('bob',))
p2 = Process(target=activite_jeux, args=('charlie',))
p1.start()
p2.start()
Résultat attendu :
bob aime le sport
charlie aime les jeux vidéo
3. Définir un processus sous forme de classe
from multiprocessing import Process
class MonProcessus(Process):
def __init__(self, nom):
super().__init__()
self.nom = nom
def run(self): # Méthode appelée automatiquement par start()
print(f"{self.nom} a été une bonne personne")
if __name__ == "__main__":
proc = MonProcessus('alice')
proc.start()
4. Effet de l'attribut daemon
Sans daemon :
import time
from multiprocessing import Process
def travail(nom):
print(f"Début du travail : {time.ctime()}")
time.sleep(2)
print(f"Fin du travail : {time.ctime()}")
if __name__ == "__main__":
p = Process(target=travail, args=('alice',))
p.start()
print("Exécution terminée ici")
Résultat : "Exécution terminée ici" s'affiche avant la fin du travail.
Avec daemon :
from multiprocessing import Process
import time
def travail(nom):
print(f"Début du travail : {time.ctime()}")
time.sleep(2)
print(f"Fin du travail : {time.ctime()}")
if __name__ == "__main__":
p = Process(target=travail, args=('alice',))
p.daemon = True # Se termine automatiquement avec le parent
p.start()
print("Exécution terminée ici")
Résultat : Seul "Exécution terminée ici" s'affiche.
Pour exécuter le travail avec daemon activé :
import time
from multiprocessing import Process
def travail(nom):
print(f"Début du travail : {time.ctime()}")
time.sleep(2)
print(f"Fin du travail : {time.ctime()}")
if __name__ == "__main__":
p = Process(target=travail, args=('alice',))
p.daemon = True
p.start()
p.join() # Attend la fin du processus
print("Exécution terminée ici")
5. Utilisation de join()
Exemple sans join :
from multiprocessing import Process
import time
import os
def travail_prolonge(nom, duree):
print(f"Ami de longue date : {nom}, PID {os.getpid()}")
time.sleep(duree)
print(f"Bon travail : {nom}")
if __name__ == "__main__":
p1 = Process(target=travail_prolonge, args=('alice', 2))
p2 = Process(target=travail_prolonge, args=('bob', 1))
p3 = Process(target=travail_prolonge, args=('charlie', 3))
p1.start()
p2.start()
p3.start()
print("Exécution terminée ici")
Résultat montre une exécution non séquentielle.
Avec join séquentiel :
from multiprocessing import Process
import time
import os
def travail_prolonge(nom, duree):
print(f"Ami de longue date : {nom}, PID {os.getpid()}")
time.sleep(duree)
print(f"Bon travail : {nom}")
if __name__ == "__main__":
debut = time.time()
p1 = Process(target=travail_prolonge, args=('alice', 2))
p2 = Process(target=travail_prolonge, args=('bob', 1))
p3 = Process(target=travail_prolonge, args=('charlie', 3))
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
print("Exécution terminée ici")
print(f"Temps écoulé : {time.time() - debut}")
Avec join parallèle :
from multiprocessing import Process
import time
import os
def travail_prolonge(nom, duree):
print(f"Ami de longue date : {nom}, PID {os.getpid()}")
time.sleep(duree)
print(f"Bon travail : {nom}")
if __name__ == "__main__":
debut = time.time()
p1 = Process(target=travail_prolonge, args=('alice', 2))
p2 = Process(target=travail_prolonge, args=('bob', 1))
p3 = Process(target=travail_prolonge, args=('charlie', 3))
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
print("Exécution terminée ici")
print(f"Temps écoulé : {time.time() - debut}")
6. Autres attributs et méthodes
Exemple avec terminate :
from multiprocessing import Process
import time
def travail(nom):
print(f"Début du travail : {time.ctime()}")
time.sleep(2)
print(f"Fin du travail : {time.ctime()}")
if __name__ == "__main__":
p = Process(target=travail, args=('alice',))
p.start()
p.terminate() # Termine brutalement le processus
Exemple d'accès aux attributs :
from multiprocessing import Process
import time
def travail(nom):
print(f"Début du travail : {time.ctime()}")
time.sleep(2)
print(f"Fin du travail : {time.ctime()}")
if __name__ == "__main__":
p = Process(target=travail, args=('alice',))
print(p.is_alive()) # False avant démarrage
p.start()
print(p.name) # Nom du processus
print(p.pid) # ID du processus
print(p.is_alive()) # True pendant l'exécution