Comment améliorer votre code Python avec la concurrence et le parallélisme

Points clés à retenir

  • La concurrence et le parallélisme sont des principes fondamentaux de l’exécution des tâches en informatique, chacun ayant ses caractéristiques distinctes.
  • La concurrence permet une utilisation efficace des ressources et une meilleure réactivité des applications, tandis que le parallélisme est crucial pour des performances et une évolutivité optimales.
  • Python fournit des options pour gérer la concurrence, telles que le threading et la programmation asynchrone avec asyncio, ainsi que le parallélisme à l’aide du module multitraitement.

La concurrence et le parallélisme sont deux techniques qui vous permettent d’exécuter plusieurs programmes simultanément. Python propose plusieurs options pour gérer les tâches simultanément et en parallèle, ce qui peut prêter à confusion.

Explorez les outils et bibliothèques disponibles pour implémenter correctement la concurrence et le parallélisme dans Python, et en quoi ils diffèrent.

Comprendre la concurrence et le parallélisme

La concurrence et le parallélisme font référence à deux principes fondamentaux de l’exécution des tâches en informatique. Chacun a ses caractéristiques distinctes.

  • La simultanéité est la capacité d’un programme à gérer plusieurs tâches en même temps sans nécessairement les exécuter exactement au même moment. Il s’articule autour de l’idée d’entrelacer les tâches, en basculant entre elles d’une manière qui semble simultanée.
  • Le parallélisme, quant à lui, implique l’exécution de plusieurs tâches véritablement en parallèle. Il tire généralement parti de plusieurs cœurs de processeur ou processeurs. Le parallélisme permet une véritable exécution simultanée, vous permettant d’effectuer des tâches plus rapidement et est bien adapté aux opérations gourmandes en calcul.
  • L’importance de la concurrence et du parallélisme

    La nécessité de la concurrence et du parallélisme dans l’informatique ne peut être surestimée. Voici pourquoi ces techniques sont importantes :

  • Utilisation des ressources : la concurrence permet une utilisation efficace des ressources système, garantissant que les tâches progressent activement plutôt que d’attendre oisivement des ressources externes.
  • Réactivité : la concurrence peut améliorer la réactivité des applications, en particulier dans les scénarios impliquant des interfaces utilisateur ou des serveurs Web.
  • Performances : le parallélisme est crucial pour obtenir des performances optimales, en particulier dans les tâches liées au processeur telles que les calculs complexes, le traitement des données et les simulations.
  • Évolutivité : la concurrence et le parallélisme sont essentiels à la création de systèmes évolutifs.
  • Pérennité : alors que les tendances matérielles continuent de favoriser les processeurs multicœurs, la capacité à exploiter le parallélisme deviendra de plus en plus nécessaire.
  • Concurrence en Python

    Vous pouvez obtenir la concurrence en Python en utilisant le threading et la programmation asynchrone avec la bibliothèque asyncio.

    Threading en Python

    Le threading est un mécanisme de concurrence Python qui vous permet de créer et de gérer des tâches au sein d’un seul processus. Les threads conviennent à certains types de tâches, en particulier celles qui sont liées aux E/S et peuvent bénéficier d’une exécution simultanée.

    Le module de thread de Python fournit une interface de haut niveau pour créer et gérer des threads. Bien que le GIL (Global Interpreter Lock) limite les threads en termes de véritable parallélisme, ils peuvent toujours atteindre la concurrence en entrelaçant efficacement les tâches.

    Le code ci-dessous montre un exemple d’implémentation de la concurrence à l’aide de threads. Il utilise la bibliothèque de requêtes Python pour envoyer une requête HTTP, une tâche courante de blocage d’E/S. Il utilise également le module time pour calculer le temps d’exécution.

     import requests
    import time
    import threading

    urls = [
        'https://www.google.com',
        'https://www.wikipedia.org',
        'https://www.makeuseof.com',
    ]


    def download_url(url):
        response = requests.get(url)
        print(f"Downloaded {url} - Status Code: {response.status_code}")


    start_time = time.time()

    for url in urls:
        download_url(url)

    end_time = time.time()
    print(f"Sequential download took {end_time - start_time:.2f} seconds\n")


    start_time = time.time()
    threads = []

    for url in urls:
        thread = threading.Thread(target=download_url, args=(url,))
        thread.start()
        threads.append(thread)


    for thread in threads:
        thread.join()

    end_time = time.time()
    print(f"Threaded download took {end_time - start_time:.2f} seconds")

    En exécutant ce programme, vous devriez voir à quel point les requêtes threadées sont plus rapides que les requêtes séquentielles. Bien que la différence ne soit qu’une fraction de seconde, vous obtenez une idée claire de l’amélioration des performances lors de l’utilisation de threads pour des tâches liées aux E/S.

    Programmation asynchrone avec Asyncio

    asyncio fournit une boucle d’événements qui gère les tâches asynchrones appelées coroutines. Les coroutines sont des fonctions que vous pouvez suspendre et reprendre, ce qui les rend idéales pour les tâches liées aux E/S. La bibliothèque est particulièrement utile pour les scénarios dans lesquels les tâches impliquent l’attente de ressources externes, telles que les requêtes réseau.

    Vous pouvez modifier l’exemple d’envoi de requête précédent pour fonctionner avec asyncio :

     import asyncio
    import aiohttp
    import time

    urls = [
        'https://www.google.com',
        'https://www.wikipedia.org',
        'https://www.makeuseof.com',
    ]


    async def download_url(url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                content = await response.text()
                print(f"Downloaded {url} - Status Code: {response.status}")


    async def main():
        
        tasks = [download_url(url) for url in urls]

        
        await asyncio.gather(*tasks)

    start_time = time.time()


    asyncio.run(main())

    end_time = time.time()

    print(f"Asyncio download took {end_time - start_time:.2f} seconds")

    À l’aide du code, vous pouvez télécharger des pages Web simultanément à l’aide d’asyncio et profiter des opérations d’E/S asynchrones. Cela peut être plus efficace que le threading pour les tâches liées aux E/S.

    Parallélisme en Python

    Vous pouvez implémenter le parallélisme en utilisant Le module multitraitement de Pythonqui vous permet de profiter pleinement des processeurs multicœurs.

    Multitraitement en Python

    Le module multitraitement de Python fournit un moyen d’obtenir le parallélisme en créant des processus distincts, chacun avec son propre interpréteur Python et son propre espace mémoire. Cela contourne efficacement le Global Interpreter Lock (GIL), ce qui le rend adapté aux tâches liées au processeur.

     import requests
    import multiprocessing
    import time

    urls = [
        'https://www.google.com',
        'https://www.wikipedia.org',
        'https://www.makeuseof.com',
    ]


    def download_url(url):
        response = requests.get(url)
        print(f"Downloaded {url} - Status Code: {response.status_code}")

    def main():
        
        num_processes = len(urls)
        pool = multiprocessing.Pool(processes=num_processes)

        start_time = time.time()
        pool.map(download_url, urls)
        end_time = time.time()

        
        pool.close()
        pool.join()

        print(f"Multiprocessing download took {end_time-start_time:.2f} seconds")

    main()

    Dans cet exemple, le multitraitement génère plusieurs processus, permettant à la fonction download_url de s’exécuter en parallèle.

    Quand utiliser la concurrence ou le parallélisme

    Le choix entre la concurrence et le parallélisme dépend de la nature de vos tâches et des ressources matérielles disponibles.

    Vous pouvez utiliser la simultanéité lorsque vous traitez des tâches liées aux E/S, telles que la lecture et l’écriture dans des fichiers ou l’exécution de requêtes réseau, et lorsque les contraintes de mémoire sont un problème.

    Utilisez le multitraitement lorsque vous avez des tâches liées au processeur qui peuvent bénéficier d’un véritable parallélisme et lorsque vous disposez d’une isolation robuste entre les tâches, où l’échec d’une tâche ne devrait pas avoir d’impact sur les autres.

    Profitez de la concurrence et du parallélisme

    Le parallélisme et la concurrence sont des moyens efficaces d’améliorer la réactivité et les performances de votre code Python. Il est important de comprendre les différences entre ces concepts et de sélectionner la stratégie la plus efficace.

    Python propose les outils et modules dont vous avez besoin pour rendre votre code plus efficace grâce à la concurrence ou au parallélisme, que vous travailliez avec des processus liés au processeur ou aux E/S.