COMBINER MULTIPROCESSING ET ASYNCIO EN PYTHON POUR DES PERFORMANCES AMÉLIORÉES
Grâce à GIL, utiliser plusieurs fils d’exécution pour effectuer des tâches liées au CPU n’a jamais été une option en Python. Cependant, avec la popularité des processeurs multicœurs, Python offre une solution de multiprocessus pour effectuer des tâches liées au CPU. Mais jusqu’à présent, il y avait encore des problèmes avec l’utilisation directe des API liées à la multiprocessus.
PROBLÈMES AVEC LA MULTIPROCESSUS
Avant de commencer, examinons un petit morceau de code pour aider à la démonstration : la méthode prend un argument et commence à accumuler de zéro jusqu’à cet argument. Elle imprime le temps d’exécution de la méthode et renvoie le résultat.
Le code montre que nous créons et démarrons directement plusieurs processus, puis appelons les méthodes start et join de chaque processus. Cependant, il y a quelques problèmes ici :
• La méthode join ne peut pas retourner le résultat de l’exécution de la tâche.
• La méthode join bloque le processus principal et l’exécute de manière séquentielle.
Même si les tâches ultérieures s’exécutent plus rapidement que les précédentes, comme le montre la figure suivante, le processus B doit encore attendre le processus A.
PROBLÈMES LIÉS À POOL
Si nous utilisons multiprocessing.Pool, il y a également des problèmes : la méthode apply de Pool est synchrone, ce qui signifie que vous devez attendre que la tâche précédente se termine avant que la tâche suivante ne puisse commencer à s’exécuter.
Bien sûr, nous pouvons utiliser la méthode apply_async pour créer la tâche de manière asynchrone. Mais encore une fois, vous devez utiliser la méthode get pour obtenir le résultat de manière bloquante. Cela nous ramène au problème avec la méthode join.
PROBLÈME AVEC L’UTILISATION DE PROCESSPOOLEXECUTOR DIRECTEMENT
Et si nous utilisons concurrent.futures.ProcesssPoolExecutor pour exécuter nos tâches liées au CPU ? Tout semble génial et est appelé exactement comme asyncio.as_completed. Mais regardez les résultats ; ils sont encore récupérés dans l’ordre de démarrage. Ce n’est pas du tout la même chose qu’asyncio.as_completed, qui obtient les résultats dans l’ordre où ils ont été exécutés.
UTILISER LA MÉTHODE RUN_IN_EXECUTOR D’ASYNCIO POUR RÉSOUDRE LES PROBLÈMES
Heureusement, nous pouvons utiliser asyncio pour gérer les tâches liées à l’entrée-sortie et sa méthode run_in_executor pour invoquer des tâches multiprocessus de la même manière qu’asyncio. Cela permet non seulement d’unifier les API concurentes et parallèles, mais aussi de résoudre les différents problèmes rencontrés ci-dessus.
Depuis que l’article précédent s’est concentré sur la simulation de ce que nous devrions appeler des méthodes de processus concurrentes, beaucoup de lecteurs ont encore besoin d’aide pour comprendre comment l’utiliser dans le codage réel après l’avoir appris. Après avoir compris pourquoi nous avons besoin d’effectuer des tâches parallèles liées au CPU dans asyncio, nous allons utiliser un exemple réel pour expliquer comment utiliser asyncio pour gérer simultanément des tâches liées à l’entrée-sortie et au CPU, et apprécier l’efficacité d’asyncio pour notre code.
Sources :
– Combining Multiprocessing and Asyncio in Python for Performance Boosts | by Peng Qian | May, 2023
– https://docs.python.org/3/library/asyncio-task.html