Le module `concurrent` en Python
Dans cet article, nous allons expliquer le module concurrent en Python.
Tout en clarifiant les concepts de concurrence et de parallélisme, nous expliquerons comment mettre en œuvre un traitement asynchrone en utilisant le module concurrent avec des exemples pratiques.
YouTube Video
Le module concurrent en Python
Lorsqu'on cherche à accélérer le traitement en Python, il est important de garder à l'esprit les différences entre la concurrence et le parallélisme. Le module concurrent constitue un moyen important de gérer de manière sûre et simple le traitement asynchrone en tenant compte de ces différences.
La différence entre la concurrence et le parallélisme
-
La concurrence consiste à concevoir un processus de sorte que plusieurs tâches avancent en basculant entre elles par petites unités de travail. Même si les tâches ne s'exécutent pas réellement en même temps, l'exploitation des "temps d'attente" permet de rendre l'ensemble du processus plus efficace.
-
Le parallélisme est un mécanisme qui exécute physiquement plusieurs tâches en même temps. En utilisant plusieurs cœurs de processeur, le traitement avance simultanément.
Les deux sont des techniques pour accélérer le traitement, mais la concurrence est une question de conception, de "comment procéder", tandis que le parallélisme est une question d'exécution, de "comment ça s'exécute", ce qui les rend fondamentalement différents.
Qu'est-ce que le module concurrent ?
concurrent est une bibliothèque standard de Python qui fournit une API de haut niveau pour gérer la concurrence et le parallélisme de manière sûre et simple. Elle est conçue pour que vous puissiez vous concentrer sur l'exécution des tâches sans avoir à vous soucier des opérations bas niveau comme la création et la gestion de threads ou de processus.
Rôles de ThreadPoolExecutor et ProcessPoolExecutor
Le module concurrent propose deux options principales selon la nature de la tâche.
-
ThreadPoolExecutorCeci convient aux implémentations concurrentes, en particulier pour les tâches comportant de nombreux temps d'attente d'entrée/sortie, comme les opérations réseau ou de fichiers. En basculant entre les tâches, il permet d'utiliser efficacement le temps d'attente. -
ProcessPoolExecutorCette implémentation vise le traitement parallèle et convient aux tâches intensives en calcul. Elle utilise plusieurs processus pour exploiter pleinement en parallèle les cœurs de CPU disponibles.
Ainsi, une des caractéristiques principales du module concurrent est qu'il offre une structure permettant de choisir correctement entre concurrence et parallélisme selon les besoins.
Bases de ThreadPoolExecutor (pour les tâches d'E/S)
ThreadPoolExecutor est adapté aux tâches liées à l'E/S, telles que la communication réseau et les opérations sur les fichiers. Il répartit les tâches entre plusieurs threads, permettant une utilisation efficace du temps d'attente.
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in futures:
13 print(future.result())- Dans cet exemple, plusieurs tâches d'E/S qui attendent une seconde sont exécutées de manière concurrente. En utilisant
submit, les appels de fonction sont enregistrés en tant que tâches asynchrones, et en appelantresult(), vous pouvez attendre l'achèvement et obtenir les résultats, ce qui permet d'implémenter un traitement concurrent qui exploite efficacement le temps d'attente de façon concise.
Concurrence simple en utilisant map
Si un contrôle complexe n'est pas nécessaire, l'utilisation de map peut rendre votre code plus concis.
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 results = executor.map(fetch_data, range(5))
11
12 for result in results:
13 print(result)- Dans cet exemple, plusieurs tâches d'E/S sont exécutées de façon concurrente à l'aide de
ThreadPoolExecutor.map. Commemapretourne les résultats dans le même ordre que les entrées, vous pouvez écrire un code proche du traitement séquentiel, et l'exécution concurrente est possible sans se soucier du traitement asynchrone — c'est un avantage important.
Bases de ProcessPoolExecutor (pour les tâches gourmandes en CPU)
Pour les calculs intensifs qui utilisent pleinement le CPU, il vaut mieux utiliser des processus plutôt que des threads. Cela permet d'éviter la limitation du verrou d'interpréteur global (GIL).
1from concurrent.futures import ProcessPoolExecutor
2
3def heavy_calculation(n):
4 # Simulate a CPU-bound task
5 total = 0
6 for i in range(10_000_000):
7 total += i * n
8 return total
9
10if __name__ == "__main__":
11 with ProcessPoolExecutor(max_workers=4) as executor:
12 results = executor.map(heavy_calculation, range(4))
13
14 for result in results:
15 print(result)Dans cet exemple, des calculs intensifs sont exécutés en parallèle à l'aide de ProcessPoolExecutor. Comme la création de processus est impliquée, une protection __main__ est nécessaire, ce qui permet un traitement parallèle sûr utilisant plusieurs cœurs de CPU.
Traitement par ordre d'achèvement avec as_completed
as_completed est utile lorsque vous souhaitez traiter les résultats dans l'ordre d'achèvement.
1from concurrent.futures import ThreadPoolExecutor, as_completed
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in as_completed(futures):
13 print(future.result())- Dans cet exemple, plusieurs tâches asynchrones sont exécutées simultanément, et les résultats sont récupérés dans l'ordre où ils se terminent. L'utilisation de
as_completedpermet de traiter les résultats rapidement indépendamment de l'ordre des tâches, ce qui le rend adapté à l'affichage de la progression ou aux situations nécessitant un traitement séquentiel.
Gestion des exceptions
Dans concurrent, les exceptions sont levées lors de l'appel à result().
1from concurrent.futures import ThreadPoolExecutor
2
3def risky_task(n):
4 # Simulate a task that may fail for a specific input
5 if n == 3:
6 raise ValueError("Something went wrong")
7 return n * 2
8
9with ThreadPoolExecutor() as executor:
10 futures = [executor.submit(risky_task, i) for i in range(5)]
11
12 for future in futures:
13 try:
14 print(future.result())
15 except Exception as e:
16 print("Error:", e)- Cet exemple montre que même si certaines tâches lèvent des exceptions, les autres poursuivent leur exécution et vous pouvez gérer les exceptions individuellement lors de la récupération des résultats. En utilisant les
Futuredeconcurrent, il est important que les succès et les échecs dans le traitement asynchrone puissent être gérés en toute sécurité.
Directives pour choisir entre threads et processus
Pour utiliser efficacement la concurrence et le parallélisme, il est important de choisir la bonne approche selon la nature de la tâche.
En pratique, les critères suivants peuvent vous aider à décider.
- Pour les processus avec beaucoup d’attentes d’E/S, tels que la communication ou les opérations sur les fichiers, utilisez
ThreadPoolExecutor. - Pour les tâches à forte charge CPU et computation intensive, utilisez
ProcessPoolExecutor. - S'il y a de nombreuses tâches simples, l'utilisation de
mappermet d'écrire un code plus concis. - Si un contrôle précis de l'ordre d'exécution ou de la gestion des exceptions est important, combinez
submitavecas_completed.
Avantages de l'utilisation de concurrent
En utilisant le module concurrent, vous pouvez gérer le traitement asynchrone de manière sûre et intuitive.
Les principaux avantages sont les suivants :.
- Vous n'avez pas à vous soucier de la gestion des threads ou des processus à bas niveau.
- Il fait partie de la bibliothèque standard de Python, vous pouvez donc l'utiliser en toute confiance.
- Le code devient plus lisible et plus facile à maintenir.
- C'est idéal comme première étape pour apprendre la concurrence et le parallélisme.
Le simple fait de garder ces lignes directrices à l'esprit peut considérablement réduire les erreurs dans les implémentations utilisant concurrent.
Résumé
Le module concurrent est l'option standard pour la concurrence et le parallélisme en Python dans la pratique. Il vous permet d'améliorer les performances sans modifier profondément le contenu de votre traitement, ce qui est un avantage important en pratique. En utilisant concurrent, vous pouvez mettre en œuvre de façon concise un traitement asynchrone tout en gérant en toute sécurité les exceptions et le contrôle de l'exécution.
Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.