
Getty Images
Tutorial zur asynchronen Programmierung in Python
Asynchrone Programmierung in Python verbessert die Effizienz von I/O-gebundenen Anwendungen, ist jedoch kein Allheilmittel. Wie asynchronen Programmierung funktioniert.
Die asynchrone Programmierung in Python ermöglicht es Programmierern, Code zu schreiben, der mehrere Aufgaben gleichzeitig ohne mehrere Threads oder Prozesse ausführen kann.
Die asyncio-Bibliothek und asynchrone Konstrukte wie Coroutinen helfen Python-Anwendungen dabei, nicht blockierende I/O-Operationen effizient auszuführen. Dies ist besonders nützlich für Aufgaben wie die Verarbeitung Tausender Netzwerkanfragen, Dateioperationen oder andere I/O-gebundene Prozesse, bei denen das Warten auf Antworten die Gesamtausführungszeit verlangsamt.
Async wird jedoch oft mit Multithreading verwechselt, was zu falschen Annahmen über seine Leistungsvorteile führt.
Async versus Multithreading: Gemeinsamkeiten und Unterschiede
Ein weit verbreiteter Irrtum ist, dass asynchrone Programmierung dasselbe ist wie Multithreading. Obwohl beide Ansätze auf Parallelität abzielen, unterscheiden sie sich grundlegend in ihrer Funktionsweise.
Beim Multithreading laufen mehrere Threads parallel und nutzen idealerweise mehrere CPU-Kerne. Global Interpreter Lock (GIL) von Python verhindert jedoch die echte parallele Ausführung von Threads, wenn mit der Standardimplementierung von Python gearbeitet wird. Infolgedessen sind Threads in Python häufig durch den Overhead des Kontextwechsels und GIL selbst eingeschränkt, insbesondere bei der Ausführung von CPU-gebundenen Aufgaben.
Bei der asynchronen Programmierung geht es hingegen nicht um Parallelität, sondern um die effiziente Verwaltung von Aufgaben, die Wartezeiten beinhalten, wie zum Beispiel I/O-Operationen. Asynchroner Code ermöglicht es einem Programm, andere Aufgaben weiter auszuführen, während es auf den Abschluss einer Operation wie einer Netzwerkabfrage oder dem Lesen einer Datei wartet.
Wichtig ist, dass Python sowohl in synchronem als auch in asynchronem Code single-threaded bleibt. Asynchrone Funktionen verwandeln Python nicht auf magische Weise in eine Parallelverarbeitungsmaschine. Stattdessen helfen sie dem einzelnen Thread, mehr Operationen gleichzeitig zu verarbeiten, indem sie Aufgaben, die auf externe Ereignisse warten, pausieren und wieder aufnehmen, wenn sie bereit sind. Dadurch eignet sich Async ideal für I/O-gebundene Aufgaben, jedoch nicht für CPU-intensive Arbeiten.
Beispiele für Python Async
Die besten Anwendungsfälle für asynchrone Programmierung in Python sind solche, die von I/O-Operationen dominiert werden. Beispiele hierfür sind:
- Web-Scraping großer Seitenmengen
- gleichzeitige API-Anfragen
- Lesen und Schreiben großer Dateien
- Verarbeitung mehrerer Client-Verbindungen in einem Webserver
- Datenbankabfragen, insbesondere über ein Netzwerk
In all diesen Szenarien wird viel Zeit damit verbracht, auf die Antwort externer Systeme zu warten. Herkömmlicher synchroner Code würde blockieren und warten und damit die Ausführung des Programms effektiv stoppen. Asynchroner Code kann jedoch viele solcher Operationen gleichzeitig initiieren und bei Eintreffen der Antworten effizient zwischen ihnen wechseln.
Wann sollte man Asynchronität in Python nicht verwenden?
Auf der anderen Seite sind Aufgaben, die überwiegend CPU-gebunden sind, für Asynchronität ungeeignet. Hier sind einige Beispiele:
- Bildverarbeitung
- Datenanalyse und mathematische Berechnungen
- Training von Modellen für maschinelles Lernen
- komplexe Algorithmen, wie Simulationen oder das Sortieren großer Datensätze
Async beschleunigt diese Aufgaben nicht, da der Engpass nicht bei der I/O, sondern beim Prozessor liegt. Versuche, Async in CPU-intensiven Szenarien zu verwenden, führen höchstwahrscheinlich zu einer Verschlechterung der Leistung und verursachen zusätzlichen Aufwand ohne Nutzen. In diesen Fällen ist Parallelität durch Multiprocessing oder andere Optionen, die GIL umgehen, ein besserer Ansatz.
Implementierung von Async in Python
Python bietet die asyncio-Bibliothek als Standardmethode zum Schreiben von asynchronem Code. Mit asyncio kann ein Entwickler Coroutinen definieren, das heißt spezielle Funktionen, die angehalten und wieder aufgenommen werden können, um gleichzeitig in einem einzigen Thread ausgeführt zu werden.
Die Grundlagen der Funktionsweise von asyncio sind wie folgt:
- async def definiert eine asynchrone Funktion, auch Coroutine genannt.
- await unterbricht die Ausführung der aktuellen Coroutine, bis die erwartete Coroutine abgeschlossen ist.
- asyncoi.run() führt die Coroutine der obersten Ebene aus und verwaltet automatisch die Ereignisschleife.
Beispiel:
import asyncio
async def fetch_data():
print("Daten werden abgerufen...")
await asyncio.sleep(2) # Simuliert eine I/O-Verzögerung
print("Daten abgerufen")
return "Beispieldaten"
async def main():
result = await fetch_data()
print(result)
asyncio.run(main())
Dieses einfache Programm ruft Daten ab, ohne die gesamte Anwendung zu blockieren. Der Aufruf stellt eine nicht blockierende Verzögerung dar, sodass andere Coroutinen während dieser Zeit ausgeführt werden können.
Coroutinen und Subprozesse
Coroutinen sind die Grundlage der asynchronen Programmierung in Python. Diese Funktionen können die Ausführung an bestimmten Punkten unterbrechen und später fortsetzen, was eine effiziente Parallelität ermöglicht. Sie sind leichtgewichtig und im Vergleich zu Threads speichereffizienter.
Subprozesse hingegen führen separate Programme oder Befehle in ihren eigenen Betriebssystemprozessen aus. In asyncio können Subprozesse asynchron verwaltet werden, sodass die Hauptanwendung reaktionsfähig bleibt, während sie auf die Fertigstellung externer Programme wartet.
Beispiel
import asyncio
async def run_command():
process = await asyncio.create_subprocess_shell(
'ls -l',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await process.communicate()
print(stdout.decode())
asyncio.run(run_command())
Netzwerk-I/O und Prozesskommunikation
Eine der leistungsstärksten Anwendungen von asyncio ist die Verarbeitung von Netzwerk-I/O. Ob es um die Verwaltung von HTTP-Anfragen, WebSockets oder TCP-Verbindungen geht, async ermöglicht die effiziente Verarbeitung von Tausenden von Verbindungen, ohne Tausende von Threads zu erzeugen.
Für die Interprozesskommunikation interagiert asyncio auch asynchron mit Pipes, Sockets und Queues, um eine nahtlose Kommunikation zwischen Prozessen zu ermöglichen, ohne die Ereignisschleife zu blockieren.
Das folgende Beispiel zeigt Netzwerk-I/O mit aiohttp:
import aiohttp
import asyncio
async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
content = await fetch_url('https://example.com')
print(content)
asyncio.run(main())
Codesynchronisation und Warteschlangen
Asynchrone Anwendungen benötigen häufig Synchronisationsmechanismen, insbesondere wenn mehrere Coroutinen gemeinsam genutzte Ressourcen erzeugen oder verbrauchen. Asyncio bietet eine asyncio.Queue, die sichere, nicht blockierende Warteschlangenoperationen unterstützt.
Im Allgemeinen wird dies mithilfe des Erzeuger-Verbraucher-Entwurfsmusters implementiert. Erzeuger können Daten zur Warteschlange hinzufügen, während Verbraucher diese gleichzeitig verarbeiten, und zwar alles innerhalb derselben Ereignisschleife.
Das folgende Beispiel zeigt, wie Daten erzeugt und verbraucht werden können, ohne den Programmablauf zu blockieren:
import asyncio
async def producer(queue):
for i in range(5):
await queue.put(i)
print(f'Produced {i}')
await asyncio.sleep(1)
async def consumer(queue):
while True:
item = await queue.get()
print(f'Consumed {item}')
queue.task_done()
async def main():
queue = asyncio.Queue()
await asyncio.gather(producer(queue), consumer(queue))
asyncio.run(main())
Async für Python: Am besten für Apps, die auf I/O angewiesen sind, nicht auf CPU
Asynchrone Programmierung in Python ist ein leistungsstarkes Tool zur Verbesserung der Leistung in I/O-gebundenen Anwendungen. Apps, die asyncio verwenden, können Tausende von Aufgaben gleichzeitig verarbeiten, was die Effizienz von Webservern, Scrapern und Netzwerkdiensten verbessert.
Es ist jedoch wichtig zu verstehen, dass async nicht Multithreading ist. Python bleibt bei der Ausführung von async-Code single-threaded, und async bietet keine Vorteile für CPU-gebundene Aufgaben. Tatsächlich kann der Versuch, async in rechenintensiven Szenarien zu verwenden, zu unnötiger Komplexität führen und die Leistung sogar beeinträchtigen.
Zusammenfassend lässt sich sagen, dass async am besten für Aufgaben geeignet ist, die Zeit mit dem Warten auf externe Ressourcen verbringen. Für CPU-gebundene Probleme sind traditionelles Multiprocessing oder kompilierte Erweiterungen nach wie vor die bessere Wahl.