cours
Tutoriel Python sur le multi-traitement
La bibliothèque standard de Python est équipée de plusieurs paquets intégrés permettant aux développeurs de commencer à profiter instantanément des avantages du langage. L'un de ces modules est le module multiprocessus, qui permet aux systèmes d'exécuter plusieurs processus simultanément. En d'autres termes, les développeurs peuvent diviser les applications en plus petits threads qui peuvent s'exécuter indépendamment de leur code Python. Ces threads ou processus sont ensuite alloués au processeur par le système d'exploitation, ce qui leur permet de s'exécuter en parallèle, améliorant ainsi les performances et l'efficacité de vos programmes Python.
Si des termes tels que threads, processus, processeurs, etc. ne vous sont pas familiers, ne vous inquiétez pas. Dans cet article, nous aborderons la définition d'un processus, la différence avec les threads et l'utilisation du module multiprocessus.
Consultez ce classeur DataLab pour suivre le code de ce tutoriel.
Que sont les processus en Python ?
Comprendre le concept de processus et de threads est extrêmement utile pour mieux comprendre comment un système d'exploitation gère les programmes à travers les différentes étapes d'exécution. Un processus n'est qu'une référence à un programme informatique. Chaque programme est associé à un processus.
Au moment où vous lisez cet article, il est très probable que votre ordinateur ait plusieurs processus en cours à tout moment, même si vous n'avez que quelques programmes ouverts - cela est dû au fait que la plupart des systèmes d'exploitation ont plusieurs tâches en cours d'exécution en arrière-plan. Par exemple, vous n'avez peut-être que trois programmes en cours d'exécution en ce moment, mais votre ordinateur peut avoir plus de 30 processus actifs en cours d'exécution simultanément.
La manière de vérifier les processus actifs en cours d'exécution sur votre ordinateur dépend de votre système d'exploitation :
- Sous Windows : Ctrl+Shift+Esc lance le gestionnaire de tâches.
- Sur Mac : ouvrez la recherche Spotlight sur Mac et tapez "Activity Monitor", puis appuyez sur Retour.
- Sous Linux : cliquez sur le menu Application et recherchez System Monitor.
Python permet d'accéder à des processus réels au niveau du système. L'instanciation d'une instance de la classe Process à partir du module multiprocessing permet aux développeurs de référencer le processus natif sous-jacent à l'aide de Python. Un nouveau processus natif est créé en coulisses lorsqu'un processus est lancé. Le cycle de vie d'un processus Python se compose de trois étapes : L'initiation d'un nouveau processus, le processus en cours d'exécution et le processus terminé - nous couvrirons chaque étape dans Les bases du module multiprocessus de Python.
Tous les processus sont constitués d'un ou de plusieurs threads. Vous rappelez-vous que nous avons mentionné qu'"un procédé n'est qu'une référence à un programme informatique" ? Eh bien, chaque programme Python est un processus qui se compose d'un thread par défaut appelé le thread principal. Le thread principal est responsable de l'exécution des instructions au sein de vos programmes Python. Cependant, il est important de noter que les processus et les fils sont différents.
Multiprocessing vs. Filetage
Pour être plus précis, une instance de l'interpréteur de Python - l'outil qui convertit le code écrit en Python en langage compréhensible par un ordinateur - équivaut à un processus. Un processus est constitué d'au moins un thread, appelé "thread principal" en Python, bien que d'autres threads puissent être créés au sein du même processus - tous les autres threads créés au sein d'un processus appartiendront à ce processus.
Le thread sert de représentation de la manière dont votre programme Python sera exécuté, et une fois que tous les threads non en arrière-plan seront terminés, le processus Python se terminera.
- Processus : Un processus est une instance de l'interpréteur Python qui se compose d'au moins un thread appelé "main thread".
- Fil : Une représentation de la manière dont un programme Python est exécuté au sein d'un processus Python.
Python dispose de deux classes extrêmement similaires qui nous permettent de mieux contrôler les processus et les threads : multiprocessing.Process et threading.Thread.
Passons en revue quelques-unes de leurs similitudes et de leurs différences.
Similitudes
#1 Concurrence
La concomitance est un concept dans lequel différentes parties du programme peuvent être exécutées hors ordre ou dans un ordre partiel sans que le résultat final en soit affecté. Les deux classes étaient initialement prévues pour la concurrence.
#2 Prise en charge des primitives de concurrence
Les classes multiprocessing.Process et threading.Thread supportent les mêmes primitives de concurrence - un outil qui permet la synchronisation et la coordination des threads et des processus.
#3 Uniform API
Une fois que vous avez compris l'API multiprocessing.Process, vous pouvez transférer ces connaissances à l'API threading.Thread, et vice versa. Ils ont été conçus intentionnellement de cette manière.
Différences
#Fonctionnalité n°1
Bien que leurs API soient identiques, les processus et les threads sont différents. Un processus est un niveau d'abstraction plus élevé qu'un fil de discussion : un processus est une référence à un programme informatique, et un fil de discussion appartient à un processus. Cette différence est inhérente aux classes. Les classes représentent donc deux fonctions natives différentes gérées par un système d'exploitation sous-jacent.
#2 Accès à l'état partagé
Les deux classes accèdent différemment à l'état partagé. Comme les threads appartiennent à un processus, ils peuvent partager la mémoire au sein de ce processus. Ainsi, une fonction exécutée dans un nouveau thread a toujours accès aux mêmes données et états au sein d'un processus. La façon dont les threads partagent les états entre eux est connue sous le nom de "mémoire partagée" et est assez simple. En revanche, le partage d'états entre processus est beaucoup plus complexe : l'état doit être sérialisé et transmis entre les processus. En d'autres termes, les processus n'utilisent pas la mémoire partagée pour partager des états, car ils disposent d'une mémoire distincte. Au lieu de cela, les processus sont partagés en utilisant une technique appelée "communication inter-processus", et pour l'exécuter en Python, il faut d'autres outils explicites comme multiprocessing.Pipe ou multiprocessing.Queue.
#3 GIL
Le verrou global de l'interprète Python (GIL) est un verrou qui permet à un seul thread de contrôler l'interprète Python. Les threads multiples sont soumis à la GIL, ce qui fait souvent de l'utilisation de Python pour effectuer du multithreading une mauvaise idée : la véritable exécution multi-cœur par le multithreading n'est pas prise en charge par Python sur l'interpréteur CPython. Toutefois, les processus ne sont pas soumis à la GIL car celle-ci est utilisée au sein de chaque processus Python mais pas entre les processus.
En termes de cas d'utilisation, le multiprocessing l'emporte souvent sur le threading dans les scénarios où le programme est intensif en termes de CPU et n'a pas besoin d'effectuer d'entrées-sorties ou d'interactions avec l'utilisateur. Le threading est la meilleure solution pour les programmes qui sont liés aux E/S ou au réseau et dans les scénarios où l'objectif est de rendre l'application plus réactive.
Les avantages du multiprocessus Python
Pensez à un processeur comme à un entrepreneur. Au fur et à mesure que l'entreprise du chef d'entreprise se développe, les tâches à gérer pour suivre la croissance de l'entreprise se multiplient. Si l'entrepreneur décide d'assumer seul toutes ces tâches (comptabilité, vente, marketing, innovation, etc.), il risque de nuire à l'efficacité et aux performances globales de l'entreprise, car une seule personne ne peut pas tout faire à la fois. Par exemple, avant de passer aux tâches d'innovation, elle doit arrêter les tâches de vente - c'est ce que l'on appelle l'exécution "séquentielle" des tâches.
La plupart des entrepreneurs savent qu'essayer de tout faire seul est une mauvaise idée. Par conséquent, elles compensent généralement le nombre croissant de tâches en engageant des employés pour gérer les différents départements. De cette manière, les tâches peuvent être effectuées en parallèle, ce qui signifie qu'il n'est pas nécessaire d'arrêter une tâche pour en exécuter une autre. Embaucher davantage de salariés pour effectuer des tâches spécifiques revient à utiliser plusieurs processeurs pour effectuer des opérations. Par exemple, les projets de vision par ordinateur sont très exigeants, car vous devez généralement traiter un grand nombre de données d'images, ce qui prend du temps : pour accélérer cette procédure, vous pouvez traiter plusieurs images en parallèle.
On peut donc dire que le multiprocessing est utile pour rendre les programmes plus efficaces en divisant et en assignant des tâches à différents processeurs. Le module multiprocessus de Python simplifie encore les choses en servant d'outil de haut niveau pour accroître l'efficacité de vos programmes en assignant des tâches à différents processus.
Les bases du module Multiprocessing de Python
Dans la section "Que sont les processus enPython?", nous avons mentionné que le cycle de vie d'un processus Python se compose de trois étapes : le nouveau processus, le processus en cours d'exécution et le processus terminé. Cette section approfondira chaque phase du cycle de vie et fournira des exemples codés.
Le nouveau processus
Un nouveau processus peut être défini comme le processus qui a été créé par l'instanciation d'une instance de la classe Process. Un processus enfant est créé lorsque nous assignons l'objet Process à une variable.
from multiprocessing import Process
# Create a new process
process = Process()
Pour l'instant, notre instance de processus ne fait rien car nous avons initialisé un objet Process vide. Nous pouvons modifier les configurations de notre objet Process pour exécuter une fonction spécifique en passant une fonction que nous voulons exécuter dans un processus différent au paramètre target de la classe.
# Create a new process with a specified function to execute.
def example_function():
pass
new_process = Process(target=example_function)
Si notre fonction cible avait également des paramètres, nous les transmettrions simplement au paramètre args de l'objet Process sous la forme d'un tuple.
Conseil : Apprenez à écrire des fonctions en Python avec le cours interactif Writing Functions in Python.
# Create a new process with specific function to execute with args.
def example(args):
pass
process = Process(target=example, args=("Hi",))
Notez que nous avons seulement créé un nouveau processus, mais qu'il n'est pas encore en cours d'exécution.
Voyons comment nous pouvons lancer un nouveau processus.
Le processus en cours
L'exécution d'un nouveau processus est assez simple : il suffit d'appeler la méthode start() de l'instance du processus.
# Run the new process
process.start()
Cette action démarre l'activité du processus en appelant la méthode run() de l'instance de processus sous le capot. La méthode run() est également chargée d'appeler la fonction personnalisée spécifiée dans le paramètre target de l'instance de processus (si elle est spécifiée).
Rappelez-vous que, plus tôt dans le tutoriel, nous avons indiqué que chaque processus possède au moins un fil d'exécution appelé fil d'exécution principal (main thread) - c'est le fil d'exécution par défaut. Ainsi, lorsqu'un processus enfant est lancé, le thread principal est créé pour ce processus enfant et démarre. Le thread principal est responsable de l'exécution de tout notre code dans le processus enfant.
Nous pouvons vérifier que notre instance de processus est vivante depuis le retour de la méthode start() jusqu'à la fin du processus enfant en utilisant la méthode is_alive() sur notre instance de processus.
Note : Si vous testez cette méthode dans un ordinateur portable, l'appel à is_alive() doit se trouver dans la même cellule que l'appel à la méthode start() pour que le processus en cours d'exécution soit pris en compte.
# Run the new process
process.start()
# Check process is running
process.is_alive()
"""
True
"""
Si le processus n'est pas en cours d'exécution, l'appel à la méthode is_alive() renvoie False.
Le processus terminé
Lorsque la fonction run() renvoie ou quitte, le processus est terminé : nous ne devons pas nécessairement faire quoi que ce soit explicitement. En d'autres termes, vous pouvez vous attendre à ce qu'un processus se termine une fois que les instructions que vous avez définies comme fonction cible sont terminées. Ainsi, la méthode is_alive() renverrait un message faux.
# Check process is terminated - should return false.
process.is_alive()
"""
False
"""
Toutefois, un processus peut également être interrompu s'il rencontre une exception non gérée ou si une erreur est soulevée. Par exemple, en cas d'erreur dans la fonction que vous avez définie comme cible, le processus s'arrête.
# Create a new process with a specific function that has an error
# to execute with args.
def example(args):
split_args = list(args.split())
# "name" variable is not in the function namespace - should raise error
return name
# New process
process = Process(target=example, args=("Hi",))
# Running the new process
process.start()
# Check process is running
process.is_alive()
"""
True
Process Process-15:
Traceback (most recent call last):
File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
self.run()
"""
L'objet Process dispose également des méthodes terminate() et kill() qui permettent aux utilisateurs de mettre fin à un processus de manière forcée.
# Create a new process with a specific function to execute with args.
def example(args):
split_args = list(args.split())
# "name" variable is not in the function namespace - should raise error
return split_args
# New process
process = Process(target=example, args=("Hi",))
# Running the new process
process.start()
if process.is_alive():
process.terminate() # You can also use process.kill()
print("Process terminated forcefully")
"""
Process terminated forcefully
"""
Il est important de noter que les terminaisons forcées ne sont pas la méthode recommandée pour mettre fin à un processus : elles peuvent ne pas fermer toutes les ressources ouvertes en toute sécurité ou ne pas stocker l'état requis du programme. Une meilleure solution consiste à utiliser un arrêt contrôlé à l'aide d'un indicateur booléen de sécurité ou d'un outil similaire.
Tutoriel Python sur le multi-traitement
Maintenant que vous comprenez les bases du multiprocessus, travaillons sur un exemple pour démontrer comment faire de la programmation concurrente en Python.
La fonction que nous créons va simplement imprimer une déclaration, dormir pendant 1 seconde, puis imprimer un autre sommeil - apprenez-en plus sur les fonctions dans ce tutoriel sur les fonctions Python.
import time
def do_something():
print("I'm going to sleep")
time.sleep(1)
print("I'm awake")
La première étape consiste à créer un nouveau processus : nous allons en créer deux.
# Create new child process
process_1 = Process(target=do_something)
process_2 = Process(target=do_something)
Voici à quoi ressemble un programme simultané dans un environnement d'ordinateur portable :
%%time
# Starts both processes
process_1.start()
process_2.start()
"""
I'm going to sleep
CPU times: user 810 µs, sys: 7.34 ms, total: 8.15 ms
Wall time: 6.04 ms
I'm going to sleep
"""
Au vu de la sortie du programme, il est évident qu'il y a un problème quelque part dans notre code. Le timer est imprimé à mi-chemin de notre premier processus, et la deuxième instruction print n'est pas imprimée.
Cela est dû au fait que trois processus sont en cours d'exécution : le processus principal, le processus_1 et le processus_2. Le processus qui consiste à suivre le temps et à l'imprimer est le processus principal. Pour que notre processus principal attende avant d'imprimer l'heure, nous devons appeler la méthode join() sur nos deux processus après les avoir lancés.
Note : Si vous souhaitez en savoir plus, consultez cette discussion sur Stackoverflow.
Jetons un coup d'œil à notre nouvel extrait de code :
%%time
# Create new child process (Cannot run a process more than once)
new_process_1 = Process(target=do_something)
new_process_2 = Process(target=do_something)
# Starts both processes
new_process_1.start()
new_process_2.start()
new_process_1.join()
new_process_2.join()
"""
I'm going to sleep
I'm going to sleep
I'm awake
I'm awake
CPU times: user 0 ns, sys: 14 ms, total: 14 ms
Wall time: 1.01 s
"""
Le problème est résolu.
Le temps de mur de cette course a été légèrement plus long que celui de la première course. Cependant, cette exécution a terminé les appels à nos deux fonctions cibles avant de renvoyer les informations sur l'heure. Nous pouvons également appliquer ce même raisonnement pour faire en sorte qu'un plus grand nombre de processus s'exécutent simultanément.
Récapitulation
Dans ce tutoriel, vous avez appris à rendre les programmes Python plus efficaces en les exécutant simultanément. Plus précisément, vous avez appris :
- Ce que sont les processus et comment vous pouvez les visualiser dans votre ordinateur.
- Les similitudes et les différences entre les modules de multiprocessing et de threading de Python.
- Les bases du module multiprocessus et comment exécuter un programme Python de manière concurrente en utilisant le multiprocessus.
Vous voulez en savoir plus sur la programmation en Python ? Consultez le cursus de programmation Python de DataCamp. Aucune expérience préalable en programmation n'est requise, et à la fin du cursus, vous aurez acquis les compétences nécessaires pour développer avec succès des logiciels, manipuler des données et effectuer des analyses avancées en Python.
Cours pour Python
cours
Introduction à la science des données en Python
cours