Heim  >  Artikel  >  Backend-Entwicklung  >  Beherrschen Sie die Asynchronität von Python: Steigern Sie die Leistung Ihrer App mit Coroutinen und Ereignisschleifen

Beherrschen Sie die Asynchronität von Python: Steigern Sie die Leistung Ihrer App mit Coroutinen und Ereignisschleifen

Barbara Streisand
Barbara StreisandOriginal
2024-11-17 08:53:03445Durchsuche

Mastering Python

Die asynchrone Programmierung von Python ist ein Game-Changer für die Erstellung von Hochleistungsanwendungen. Ich benutze es seit Jahren und es überrascht mich immer wieder, wie kraftvoll es sein kann, wenn es richtig angewendet wird.

Das Herzstück des asynchronen Modells von Python sind Coroutinen und Ereignisschleifen. Coroutinen sind spezielle Funktionen, die ihre Ausführung anhalten und fortsetzen können, was effizientes Multitasking ohne den Overhead von Threads ermöglicht. Ereignisschleifen hingegen sind die Engines, die diese Coroutinen steuern, ihre Ausführung verwalten und I/O-Vorgänge abwickeln.

Beginnen wir mit Coroutinen. In Python definieren wir sie mithilfe der async def-Syntax. Hier ist ein einfaches Beispiel:

async def greet(name):
    print(f"Hello, {name}!")
    await asyncio.sleep(1)
    print(f"Goodbye, {name}!")

Diese Coroutine begrüßt eine Person, wartet eine Sekunde und verabschiedet sich dann. Das Schlüsselwort „await“ ist hier von entscheidender Bedeutung – es ermöglicht der Coroutine, ihre Ausführung anzuhalten und die Kontrolle wieder an die Ereignisschleife zu übergeben.

Aber wie funktionieren Coroutinen unter der Haube? Sie basieren tatsächlich auf der Generatorfunktionalität von Python. Wenn Sie eine Coroutine aufrufen, wird sie nicht sofort ausgeführt. Stattdessen wird ein Coroutine-Objekt zurückgegeben. Dieses Objekt kann Werte senden und Werte liefern, genau wie ein Generator.

Die Ereignisschleife ist für die tatsächliche Ausführung dieser Coroutinen verantwortlich. Es verwaltet eine Warteschlange von Aufgaben (die Coroutinen umhüllen) und führt sie einzeln aus. Wenn eine Coroutine auf eine Wait-Anweisung trifft, wird sie von der Ereignisschleife angehalten und mit der nächsten Aufgabe fortgefahren. Das ist die Essenz des kooperativen Multitaskings – Aufgaben geben freiwillig die Kontrolle ab und überlassen es anderen, ausgeführt zu werden.

Hier ist eine vereinfachte Version, wie eine Ereignisschleife funktionieren könnte:

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

Diese Ereignisschleife verwaltet zwei Arten von Aufgaben: diejenigen, die zur Ausführung bereit sind (in der Bereitschaftsdeque) und solche, die sich im Ruhezustand befinden (in der Schlafliste). Die run_forever-Methode führt so lange Aufgaben aus, bis keine mehr übrig sind.

Lassen Sie uns nun über die Aufgabenplanung sprechen. Das Asyncio-Modul in Python bietet eine ausgefeiltere Ereignisschleife mit erweiterten Planungsfunktionen. Es kann E/A-Vorgänge verarbeiten, Unterprozesse ausführen und sogar in andere Ereignisschleifen integrieren.

So können Sie Asyncio verwenden, um mehrere Coroutinen gleichzeitig auszuführen:

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())

Dieses Skript startet beide Aufgaben, aber Aufgabe 2 wird vor Aufgabe 1 beendet, da es kürzer im Ruhezustand ist.

Eine der leistungsstärksten Anwendungen der asynchronen Programmierung ist der Netzwerkbetrieb. Schauen wir uns einen einfachen asynchronen Webserver an:

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())

Dieser Server kann mehrere Clients gleichzeitig verwalten, ohne Threads zu verwenden, was ihn äußerst effizient macht.

Aber asynchrone Programmierung ist nicht nur für Server geeignet. Es eignet sich auch hervorragend für Kunden, insbesondere wenn Sie mehrere Netzwerkanfragen stellen müssen. Hier ist ein einfacher Web-Scraper, der mehrere Seiten gleichzeitig abrufen kann:

async def greet(name):
    print(f"Hello, {name}!")
    await asyncio.sleep(1)
    print(f"Goodbye, {name}!")

Dieser Scraper kann mehrere Seiten gleichzeitig abrufen, was den Vorgang im Vergleich zu einem synchronen Ansatz erheblich beschleunigt.

Lassen Sie uns nun in einige fortgeschrittenere Konzepte eintauchen. Eine interessante Funktion des asynchronen Modells von Python besteht darin, dass Sie Ihre eigenen Ereignisschleifen erstellen können. Dies kann nützlich sein, wenn Sie asynchronen Code in andere Frameworks integrieren müssen oder für bestimmte Anwendungsfälle optimieren möchten.

Hier ist eine einfache benutzerdefinierte Ereignisschleife, die sowohl synchrone als auch asynchrone Rückrufe ausführen kann:

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

Diese benutzerdefinierte Schleife ist sehr einfach, demonstriert jedoch die Grundprinzipien. Sie können dies erweitern, um komplexere Szenarien wie E/A-Vorgänge oder Timer zu verarbeiten.

Das Debuggen von asynchronem Code kann eine Herausforderung sein, insbesondere wenn Sie mit komplexen Anwendungen arbeiten. Eine Technik, die ich hilfreich finde, ist die Verwendung des Debug-Modus von Asyncio. Sie können es wie folgt aktivieren:

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())

Dadurch erhalten Sie detailliertere Fehlermeldungen und Warnungen zu Dingen wie Coroutinen, die nie abgeschlossen werden, oder Rückrufen, deren Ausführung zu lange dauert.

Eine weitere nützliche Debugging-Technik ist die Verwendung der Task-Introspection-Funktionen von Asyncio. Sie können beispielsweise eine Liste aller laufenden Aufgaben erhalten:

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())

Dies kann Ihnen helfen zu verstehen, was Ihr Programm zu einem bestimmten Zeitpunkt tut.

Wenn es um die Optimierung von asynchronem Code geht, besteht ein Schlüsselprinzip darin, den Zeitaufwand für synchrone Vorgänge zu minimieren. Jeder synchrone Code mit langer Laufzeit blockiert die Ereignisschleife und verhindert so die Ausführung anderer Coroutinen. Wenn Sie CPU-intensive Aufgaben haben, sollten Sie erwägen, diese in einem separaten Thread oder Prozess auszuführen.

Eine weitere Optimierungstechnik ist die Verwendung von asyncio.gather, wenn Sie über mehrere Coroutinen verfügen, die gleichzeitig ausgeführt werden können. Das ist effizienter, als sie einzeln abzuwarten:

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())

Denken Sie schließlich daran, dass asynchrone Programmierung nicht immer die beste Lösung ist. Bei E/A-gebundenen Aufgaben mit vielen Wartezeiten kann es zu erheblichen Leistungsverbesserungen führen. Aber für CPU-gebundene Aufgaben könnte traditionelles Multithreading oder Multiprocessing besser geeignet sein.

Zusammenfassend lässt sich sagen, dass das asynchrone Programmiermodell von Python, das auf Coroutinen und Ereignisschleifen basiert, eine leistungsstarke Möglichkeit zum Schreiben effizienter, skalierbarer Anwendungen bietet. Unabhängig davon, ob Sie Webserver, Netzwerk-Clients oder Datenverarbeitungspipelines erstellen, kann Ihnen das Verständnis dieser Konzepte dabei helfen, die asynchronen Funktionen von Python optimal zu nutzen. Wie bei jedem leistungsstarken Werkzeug erfordert der effektive Einsatz Übung und sorgfältige Überlegungen, aber die Ergebnisse können wirklich beeindruckend sein.


Unsere Kreationen

Schauen Sie sich unbedingt unsere Kreationen an:

Investor Central | Intelligentes Leben | Epochen & Echos | Rätselhafte Geheimnisse | Hindutva | Elite-Entwickler | JS-Schulen


Wir sind auf Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Wissenschaft & Epochen Medium | Modernes Hindutva

Das obige ist der detaillierte Inhalt vonBeherrschen Sie die Asynchronität von Python: Steigern Sie die Leistung Ihrer App mit Coroutinen und Ereignisschleifen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn