Maison >développement back-end >Tutoriel Python >Python haute performance : Asyncio

Python haute performance : Asyncio

Susan Sarandon
Susan Sarandonoriginal
2025-01-01 06:30:09939parcourir

High-Performance Python: Asyncio

La programmation simultanée est une approche de programmation qui traite de l'exécution simultanée de plusieurs tâches. En Python, asyncio est un outil puissant pour implémenter la programmation asynchrone. Basé sur le concept de coroutines, asyncio peut gérer efficacement les tâches gourmandes en E/S. Cet article présentera les principes de base et l'utilisation d'asyncio.

High-Performance Python: Asyncio

Pourquoi nous avons besoin d'asyncio

Nous savons que lors de la gestion des opérations d'E/S, l'utilisation du multithreading peut considérablement améliorer l'efficacité par rapport à un seul thread normal. Alors, pourquoi avons-nous encore besoin d'asyncio ?

Le multithreading présente de nombreux avantages et est largement utilisé, mais il présente également certaines limites :

  • Par exemple, le processus en cours d'exécution du multithreading est facilement interrompu, de sorte qu'une situation de situation de concurrence critique peut se produire.
  • De plus, le changement de thread lui-même a un certain coût, et le nombre de threads ne peut pas être augmenté indéfiniment. Par conséquent, si vos opérations d'E/S sont très lourdes, le multithreading ne parviendra probablement pas à répondre aux exigences de haute efficacité et de haute qualité.

C'est précisément pour résoudre ces problèmes qu'asyncio a émergé.

Synchronisation VS Async

Distinguons d'abord les concepts de Sync (synchrone) et d'Async (asynchrone).

  • Sync signifie que les opérations sont exécutées les unes après les autres. L'opération suivante ne peut être exécutée qu'une fois la précédente terminée.
  • Async signifie que différentes opérations peuvent être exécutées alternativement. Si l'une des opérations est bloquée, le programme n'attendra pas mais trouvera des opérations exécutables pour continuer.

Comment fonctionne asyncio

  1. Coroutines : asyncio utilise des coroutines pour réaliser des opérations asynchrones. Une coroutine est une fonction spéciale définie avec le mot-clé async. Dans une coroutine, le mot-clé wait peut être utilisé pour suspendre l'exécution de la coroutine actuelle et attendre la fin d'une opération asynchrone.
  2. Event Loop : La boucle d'événements est l'un des mécanismes de base d'asyncio. Il est responsable de la planification et de l’exécution des coroutines et de la gestion de la commutation entre les coroutines. La boucle d'événements interrogera constamment les tâches exécutables. Une fois qu'une tâche est prête (par exemple lorsqu'une opération d'E/S est terminée ou qu'un minuteur expire), la boucle d'événements la placera dans la file d'attente d'exécution et passera à la tâche suivante.
  3. Tâches asynchrones : En asyncio, nous exécutons des coroutines en créant des tâches asynchrones. Les tâches asynchrones sont créées par la fonction asyncio.create_task(), qui encapsule la coroutine dans un objet attendu et la soumet à la boucle d'événements pour traitement.
  4. Opérations d'E/S asynchrones : asyncio fournit un ensemble d'opérations d'E/S asynchrones (telles que les requêtes réseau, la lecture et l'écriture de fichiers, etc.), qui peuvent être intégrées de manière transparente aux coroutines et à la boucle d'événements via le mot-clé wait. En utilisant des opérations d'E/S asynchrones, le blocage pendant l'attente de la fin des E/S peut être évité, améliorant ainsi les performances et la concurrence du programme.
  5. Rappels : asyncio prend également en charge l'utilisation de fonctions de rappel pour gérer les résultats des opérations asynchrones. La fonction asyncio.ensure_future() peut être utilisée pour encapsuler la fonction de rappel dans un objet attendu et la soumettre à la boucle d'événements pour traitement.
  6. Exécution simultanée : asyncio peut exécuter simultanément plusieurs tâches coroutine. La boucle d'événements planifiera automatiquement l'exécution des coroutines en fonction de l'état de préparation des tâches, permettant ainsi une programmation simultanée efficace.

En résumé, le principe de fonctionnement d'asyncio repose sur les mécanismes de coroutines et de boucles d'événements. En utilisant des coroutines pour les opérations asynchrones et en confiant la boucle d'événements responsable de la planification et de l'exécution des coroutines, asyncio réalise un modèle de programmation asynchrone efficace.

Coroutines et programmation asynchrone

Les coroutines sont un concept important en asyncio. Ce sont des unités d'exécution légères qui peuvent basculer rapidement entre les tâches sans la surcharge de changement de thread. Les coroutines peuvent être définies avec le mot-clé async, et le mot-clé wait est utilisé pour suspendre l'exécution de la coroutine et reprendre une fois une certaine opération terminée.

Voici un exemple de code simple montrant comment utiliser les coroutines pour la programmation asynchrone :

import asyncio

async def hello():
    print("Hello")
    await asyncio.sleep(1)  # Simulate a time-consuming operation
    print("World")

# Create an event loop
loop = asyncio.get_event_loop()

# Add the coroutine to the event loop and execute
loop.run_until_complete(hello())

Dans cet exemple, la fonction hello() est une coroutine définie avec le mot-clé async. À l’intérieur de la coroutine, nous pouvons utiliser wait pour suspendre son exécution. Ici, asyncio.sleep(1) est utilisé pour simuler une opération fastidieuse. La méthode run_until_complete() ajoute la coroutine à la boucle d'événements et l'exécute.

Opérations d'E/S asynchrones

asyncio est principalement utilisé pour gérer les tâches gourmandes en E/S, telles que les requêtes réseau, la lecture et l'écriture de fichiers. Il fournit une série d'API pour les opérations d'E/S asynchrones, qui peuvent être utilisées en combinaison avec le mot-clé wait pour réaliser facilement une programmation asynchrone.

Voici un exemple de code simple montrant comment utiliser asyncio pour les requêtes réseau asynchrones :

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'https://www.example.com')
        print(html)

# Create an event loop
loop = asyncio.get_event_loop()

# Add the coroutine to the event loop and execute
loop.run_until_complete(main())

Dans cet exemple, nous utilisons la bibliothèque aiohttp pour les requêtes réseau. La fonction fetch() est une coroutine. Il lance une requête GET asynchrone via la méthode session.get() et attend le retour de la réponse à l'aide du mot-clé wait. La fonction main() est une autre coroutine. Il crée un objet ClientSession à l'intérieur pour le réutiliser, puis appelle la méthode fetch() pour obtenir le contenu de la page Web et l'imprimer.

Remarque : Ici, nous utilisons aiohttp au lieu de la bibliothèque de requêtes car la bibliothèque de requêtes n'est pas compatible avec asyncio, alors que la bibliothèque aiohttp l'est. Pour faire bon usage d'asyncio, notamment pour exercer ses puissantes fonctions, dans de nombreux cas, des bibliothèques Python correspondantes sont nécessaires.

Exécution simultanée de plusieurs tâches

asyncio fournit également certains mécanismes pour exécuter simultanément plusieurs tâches, tels que asyncio.gather() et asyncio.wait(). Voici un exemple de code montrant comment utiliser ces mécanismes pour exécuter simultanément plusieurs tâches de coroutine :

import asyncio

async def task1():
    print("Task 1 started")
    await asyncio.sleep(1)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(2)
    print("Task 2 finished")

async def main():
    await asyncio.gather(task1(), task2())

# Create an event loop
loop = asyncio.get_event_loop()

# Add the coroutine to the event loop and execute
loop.run_until_complete(main())

Dans cet exemple, nous définissons deux tâches coroutines task1() et task2(), qui effectuent toutes deux des opérations fastidieuses. La coroutine main() démarre ces deux tâches simultanément via asyncio.gather() et attend qu'elles se terminent. L'exécution simultanée peut améliorer l'efficacité de l'exécution du programme.

Comment choisir ?

Dans les projets réels, faut-il choisir le multithreading ou l'asyncio ? Un gros plan l'a résumé de manière vivante :

import asyncio

async def hello():
    print("Hello")
    await asyncio.sleep(1)  # Simulate a time-consuming operation
    print("World")

# Create an event loop
loop = asyncio.get_event_loop()

# Add the coroutine to the event loop and execute
loop.run_until_complete(hello())
  • S'il est lié aux E/S et que les opérations d'E/S sont lentes, nécessitant la coopération de nombreuses tâches/threads, alors l'utilisation d'asyncio est plus appropriée.
  • S'il est lié aux E/S mais que les opérations d'E/S sont rapides et que seul un nombre limité de tâches/threads sont nécessaires, alors le multithread fera l'affaire.
  • S'il est lié au processeur, un multitraitement est nécessaire pour améliorer l'efficacité de l'exécution du programme.

Pratique

Saisissez une liste. Pour chaque élément de la liste, nous voulons calculer la somme des carrés de tous les entiers de 0 à cet élément.

Implémentation synchrone

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'https://www.example.com')
        print(html)

# Create an event loop
loop = asyncio.get_event_loop()

# Add the coroutine to the event loop and execute
loop.run_until_complete(main())

Le temps d'exécution est Le calcul prend 16,00943413000002 secondes

Implémentation asynchrone avec concurrent.futures

import asyncio

async def task1():
    print("Task 1 started")
    await asyncio.sleep(1)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(2)
    print("Task 2 finished")

async def main():
    await asyncio.gather(task1(), task2())

# Create an event loop
loop = asyncio.get_event_loop()

# Add the coroutine to the event loop and execute
loop.run_until_complete(main())

Le temps d'exécution est Le calcul prend 7,314132894999999 secondes

Dans ce code amélioré, nous utilisons concurrent.futures.ProcessPoolExecutor pour créer un pool de processus, puis utilisons la méthode executor.map() pour soumettre des tâches et obtenir des résultats. Notez qu'après avoir utilisé executor.map(), si vous avez besoin d'obtenir les résultats, vous pouvez parcourir les résultats dans une liste ou utiliser d'autres méthodes pour traiter les résultats.

Implémentation du multitraitement

if io_bound:
    if io_slow:
        print('Use Asyncio')
    else:
        print('Use multi-threading')
elif cpu_bound:
    print('Use multi-processing')

Le temps d'exécution est Le calcul prend 5,024221667 secondes

concurrent.futures.ProcessPoolExecutor et multiprocessing sont tous deux des bibliothèques permettant d'implémenter la concurrence multi-processus en Python. Il y a quelques différences :

  1. Encapsulation basée sur l'interface : concurrent.futures.ProcessPoolExecutor est une interface de haut niveau fournie par le module concurrent.futures. Il encapsule les fonctions multi-processus sous-jacentes, ce qui facilite l'écriture de code multi-processus. Alors que le multitraitement est l'une des bibliothèques standard de Python, fournissant une prise en charge multi-processus complète et permettant une opération directe sur les processus.
  2. Utilisation de l'API : L'utilisation de concurrent.futures.ProcessPoolExecutor est similaire à celle d'un pool de threads. Il soumet des objets appelables (tels que des fonctions) au pool de processus pour exécution et renvoie un objet Future, qui peut être utilisé pour obtenir le résultat de l'exécution. le multitraitement fournit davantage d'interfaces de gestion de processus et de communication de bas niveau. Les processus peuvent être explicitement créés, démarrés et contrôlés, et la communication entre plusieurs processus peut être effectuée à l'aide de files d'attente ou de canaux.
  3. Évolutivité et flexibilité : étant donné que le multitraitement fournit davantage d'interfaces de bas niveau, il est plus flexible que concurrent.futures.ProcessPoolExecutor. En exploitant directement les processus, un contrôle plus fin peut être obtenu pour chaque processus, comme la définition des priorités des processus et le partage des données entre les processus. concurrent.futures.ProcessPoolExecutor est plus adapté à la parallélisation de tâches simples, masquant de nombreux détails sous-jacents et facilitant l'écriture de code multi-processus.
  4. Prise en charge multiplateforme : concurrent.futures.ProcessPoolExecutor et multiprocessing fournissent une prise en charge multi-processus multiplateforme et peuvent être utilisés sur différents systèmes d'exploitation.

En résumé, concurrent.futures.ProcessPoolExecutor est une interface de haut niveau qui encapsule les fonctions multi-processus sous-jacentes, adaptée à une parallélisation simple de tâches multi-processus. multiprocessing est une bibliothèque de plus bas niveau, offrant plus de contrôle et de flexibilité, adaptée aux scénarios nécessitant un contrôle plus fin des processus. Vous devez choisir la bibliothèque appropriée en fonction d'exigences spécifiques. S'il s'agit simplement d'une simple parallélisation de tâches, vous pouvez utiliser concurrent.futures.ProcessPoolExecutor pour simplifier le code ; si davantage de contrôle et de communication de bas niveau sont nécessaires, vous pouvez utiliser la bibliothèque multitraitement.

Résumé

Contrairement au multithreading, asyncio est monothread, mais le mécanisme de sa boucle d'événements interne lui permet d'exécuter plusieurs tâches différentes simultanément et a un plus grand contrôle autonome que le multithreading.

Les tâches en asyncio ne seront pas interrompues pendant le fonctionnement, donc la situation de condition de concurrence ne se produira pas.

Particulièrement dans les scénarios avec des opérations d'E/S lourdes, asyncio a une efficacité opérationnelle plus élevée que le multithreading. Parce que le coût du changement de tâche en asyncio est bien inférieur à celui du changement de thread, et le nombre de tâches qu'asyncio peut démarrer est bien plus grand que le nombre de threads en multithreading.

Cependant, il convient de noter que dans de nombreux cas, l'utilisation d'asyncio nécessite le support de bibliothèques tierces spécifiques, comme aiohttp dans l'exemple précédent. Et si les opérations d'E/S sont rapides et peu lourdes, l'utilisation du multithreading peut également résoudre efficacement le problème.

  • asyncio est une bibliothèque Python pour implémenter la programmation asynchrone.
  • Les coroutines sont le concept de base d'asyncio, réalisant des opérations asynchrones via les mots-clés async et wait.
  • asyncio fournit une API puissante pour les opérations d'E/S asynchrones et peut facilement gérer les tâches gourmandes en E/S.
  • Grâce à des mécanismes tels que asyncio.gather(), plusieurs tâches de coroutine peuvent être exécutées simultanément.

Leapcell : la plate-forme idéale pour FastAPI, Flask et autres applications Python

Enfin, permettez-moi de vous présenter la plateforme idéale pour déployer Flask/FastAPI : Leapcell.

Leapcell est une plateforme de cloud computing spécialement conçue pour les applications distribuées modernes. Son modèle de tarification à l'utilisation garantit l'absence de coûts inutiles, ce qui signifie que les utilisateurs ne paient que pour les ressources qu'ils utilisent réellement.

High-Performance Python: Asyncio

  1. Support multilingue
    • Prend en charge le développement en JavaScript, Python, Go ou Rust.
  2. Déploiement gratuit de projets illimités
    • Frais uniquement en fonction de l'utilisation. Aucun frais lorsqu'il n'y a aucune demande.
  3. Rentabilité inégalée
    • Payez à l'utilisation, sans frais d'inactivité.
    • Par exemple, 25 $ peuvent prendre en charge 6,94 millions de requêtes, avec un temps de réponse moyen de 60 millisecondes.
  4. Expérience de développeur simplifiée
    • Interface utilisateur intuitive pour une configuration facile.
    • Pipelines CI/CD entièrement automatisés et intégration GitOps.
    • Mesures et journaux en temps réel, fournissant des informations exploitables.
  5. Évolutivité sans effort et hautes performances
    • Mise à l'échelle automatique pour gérer facilement une concurrence élevée.
    • Zéro surcharge opérationnelle, permettant aux développeurs de se concentrer sur le développement.

Apprenez-en plus dans la documentation !
Twitter de Leapcell : https://x.com/LeapcellHQ

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn