Maîtriser l'async de Python : améliorez les performances de votre application avec des coroutines et des boucles d'événements

Barbara Streisand
Libérer: 2024-11-17 08:53:03
original
444 Les gens l'ont consulté

Mastering Python

La programmation asynchrone de Python change la donne pour la création d'applications hautes performances. Je l'utilise depuis des années et je suis toujours étonné de voir à quel point il peut être puissant lorsqu'il est utilisé correctement.

Au cœur du modèle asynchrone de Python se trouvent les coroutines et les boucles d'événements. Les coroutines sont des fonctions spéciales qui peuvent suspendre et reprendre leur exécution, permettant un multitâche efficace sans la surcharge des threads. Les boucles d'événements, quant à elles, sont les moteurs qui pilotent ces coroutines, gérant leur exécution et gérant les opérations d'E/S.

Commençons par les coroutines. En Python, nous les définissons en utilisant la syntaxe async def. Voici un exemple simple :

async def greet(name):
    print(f"Hello, {name}!")
    await asyncio.sleep(1)
    print(f"Goodbye, {name}!")
Copier après la connexion
Copier après la connexion

Cette coroutine salue une personne, attend une seconde, puis lui dit au revoir. Le mot-clé wait est crucial ici : il permet à la coroutine de suspendre son exécution et de redonner le contrôle à la boucle d'événements.

Mais comment fonctionnent les coroutines sous le capot ? Ils sont en fait construits sur la fonctionnalité du générateur de Python. Lorsque vous appelez une coroutine, elle ne s'exécute pas immédiatement. Au lieu de cela, il renvoie un objet coroutine. Cet objet peut recevoir des valeurs et générer des valeurs, tout comme un générateur.

La boucle d'événements est responsable de l'exécution réelle de ces coroutines. Il maintient une file d'attente de tâches (qui enveloppent les coroutines) et les exécute une par une. Lorsqu'une coroutine atteint une instruction wait, la boucle d'événements la suspend et passe à la tâche suivante. C'est l'essence du multitâche coopératif : les tâches abandonnent volontairement le contrôle, permettant aux autres de s'exécuter.

Voici une version simplifiée du fonctionnement d'une boucle d'événements :

class EventLoop:
    def __init__(self):
        self.ready = deque()
        self.sleeping = []

    def call_soon(self, callback):
        self.ready.append(callback)

    def call_later(self, delay, callback):
        deadline = time.time() + delay
        heapq.heappush(self.sleeping, (deadline, callback))

    def run_forever(self):
        while True:
            self.run_once()

    def run_once(self):
        now = time.time()
        while self.sleeping and self.sleeping[0][0] <= now:
            _, callback = heapq.heappop(self.sleeping)
            self.ready.append(callback)

        if self.ready:
            callback = self.ready.popleft()
            callback()
        else:
            time.sleep(0.1)  # Avoid busy waiting
Copier après la connexion
Copier après la connexion

Cette boucle d'événements gère deux types de tâches : celles qui sont prêtes à s'exécuter (dans le deque prêt) et celles qui sont en veille (dans la liste en veille). La méthode run_forever continue d'exécuter des tâches jusqu'à ce qu'il n'en reste plus.

Parlons maintenant de la planification des tâches. Le module asyncio en Python fournit une boucle d'événements plus sophistiquée avec des capacités de planification avancées. Il peut gérer les opérations d'E/S, exécuter des sous-processus et même s'intégrer à d'autres boucles d'événements.

Voici comment utiliser asyncio pour exécuter plusieurs coroutines simultanément :

import asyncio

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

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

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

asyncio.run(main())
Copier après la connexion
Copier après la connexion

Ce script démarrera les deux tâches, mais la tâche 2 se terminera avant la tâche 1 car elle dort moins longtemps.

L'une des applications les plus puissantes de la programmation asynchrone réside dans les opérations réseau. Regardons un simple serveur Web asynchrone :

import asyncio

async def handle_client(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')

    print(f"Received {message!r} from {addr!r}")

    response = f"Echo: {message}\n"
    writer.write(response.encode())
    await writer.drain()

    print("Close the connection")
    writer.close()

async def main():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()

asyncio.run(main())
Copier après la connexion
Copier après la connexion

Ce serveur peut gérer plusieurs clients simultanément sans utiliser de threads, ce qui le rend très efficace.

Mais la programmation asynchrone n'est pas réservée aux serveurs. C'est également idéal pour les clients, en particulier lorsque vous devez effectuer plusieurs requêtes réseau. Voici un simple grattoir Web qui peut récupérer plusieurs pages simultanément :

async def greet(name):
    print(f"Hello, {name}!")
    await asyncio.sleep(1)
    print(f"Goodbye, {name}!")
Copier après la connexion
Copier après la connexion

Ce scraper peut récupérer plusieurs pages simultanément, accélérant considérablement le processus par rapport à une approche synchrone.

Plongeons maintenant dans quelques concepts plus avancés. Une fonctionnalité intéressante du modèle asynchrone de Python est que vous pouvez créer vos propres boucles d'événements. Cela peut être utile si vous devez intégrer du code asynchrone avec d'autres frameworks ou si vous souhaitez l'optimiser pour des cas d'utilisation spécifiques.

Voici une simple boucle d'événements personnalisée qui peut exécuter des rappels synchrones et asynchrones :

class EventLoop:
    def __init__(self):
        self.ready = deque()
        self.sleeping = []

    def call_soon(self, callback):
        self.ready.append(callback)

    def call_later(self, delay, callback):
        deadline = time.time() + delay
        heapq.heappush(self.sleeping, (deadline, callback))

    def run_forever(self):
        while True:
            self.run_once()

    def run_once(self):
        now = time.time()
        while self.sleeping and self.sleeping[0][0] <= now:
            _, callback = heapq.heappop(self.sleeping)
            self.ready.append(callback)

        if self.ready:
            callback = self.ready.popleft()
            callback()
        else:
            time.sleep(0.1)  # Avoid busy waiting
Copier après la connexion
Copier après la connexion

Cette boucle personnalisée est très basique, mais elle démontre les principes de base. Vous pouvez étendre cela pour gérer des scénarios plus complexes, comme les opérations d'E/S ou les minuteries.

Le débogage du code asynchrone peut être difficile, surtout lorsque vous avez affaire à des applications complexes. Une technique que je trouve utile consiste à utiliser le mode de débogage d’asyncio. Vous pouvez l'activer comme ceci :

import asyncio

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

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

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

asyncio.run(main())
Copier après la connexion
Copier après la connexion

Cela fournira des messages d'erreur et des avertissements plus détaillés sur des éléments tels que les coroutines qui ne se terminent jamais ou les rappels qui prennent trop de temps à s'exécuter.

Une autre technique de débogage utile consiste à utiliser les fonctionnalités d'introspection des tâches d'asyncio. Par exemple, vous pouvez obtenir une liste de toutes les tâches en cours :

import asyncio

async def handle_client(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')

    print(f"Received {message!r} from {addr!r}")

    response = f"Echo: {message}\n"
    writer.write(response.encode())
    await writer.drain()

    print("Close the connection")
    writer.close()

async def main():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()

asyncio.run(main())
Copier après la connexion
Copier après la connexion

Cela peut vous aider à comprendre ce que fait votre programme à un moment donné.

Lorsqu'il s'agit d'optimiser le code asynchrone, un principe clé est de minimiser le temps passé dans les opérations synchrones. Tout code synchrone de longue durée bloquera la boucle d'événements, empêchant ainsi l'exécution d'autres coroutines. Si vous avez des tâches gourmandes en CPU, envisagez de les exécuter dans un thread ou un processus distinct.

Une autre technique d'optimisation consiste à utiliser asyncio.gather lorsque vous disposez de plusieurs coroutines pouvant s'exécuter simultanément. C'est plus efficace que de les attendre un par un :

import asyncio
import aiohttp

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

async def main():
    urls = [
        'http://example.com',
        'http://example.org',
        'http://example.net'
    ]

    async with aiohttp.ClientSession() as session:
        tasks = [fetch_page(session, url) for url in urls]
        pages = await asyncio.gather(*tasks)

    for url, page in zip(urls, pages):
        print(f"Page from {url}: {len(page)} bytes")

asyncio.run(main())
Copier après la connexion

Enfin, n'oubliez pas que la programmation asynchrone n'est pas toujours la meilleure solution. Pour les tâches liées aux E/S avec beaucoup d’attente, cela peut apporter des améliorations significatives des performances. Mais pour les tâches liées au processeur, le multithreading ou le multitraitement traditionnel pourrait être plus approprié.

En conclusion, le modèle de programmation asynchrone de Python, construit sur des coroutines et des boucles d'événements, offre un moyen puissant d'écrire des applications efficaces et évolutives. Que vous construisiez des serveurs Web, des clients réseau ou des pipelines de traitement de données, comprendre ces concepts peut vous aider à tirer pleinement parti des fonctionnalités asynchrones de Python. Comme tout outil puissant, son utilisation efficace nécessite de la pratique et une réflexion approfondie, mais les résultats peuvent être vraiment impressionnants.


Nos créations

N'oubliez pas de consulter nos créations :

Centre des investisseurs | Vie intelligente | Époques & Échos | Mystères déroutants | Hindutva | Développeur Élite | Écoles JS


Nous sommes sur Medium

Tech Koala Insights | Epoques & Echos Monde | Support Central des Investisseurs | Mystères déroutants Medium | Sciences & Epoques Medium | Hindutva moderne

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!

source:dev.to
Déclaration de ce site Web
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
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal