Python: Asynchrone Verarbeitung erklärt – Guide für Anfänger bis Mittelstufe

目次

1. Einführung

Python wird von vielen Entwicklern wegen seiner einfachen Syntax und leistungsstarken Bibliotheken geschätzt. Darunter ist „asynchrone Verarbeitung“ eine wichtige Technik, um Aufgaben effizient zu bearbeiten. In diesem Artikel erklären wir die Grundlagen bis hin zu fortgeschrittenen Anwendungen der asynchronen Verarbeitung in Python auf verständliche Weise. Durch das Verständnis der asynchronen Verarbeitung lernen Sie, wie Sie die Geschwindigkeit von Web-Scraping oder API-Anfragen erheblich verbessern können.

2. Grundlagen der asynchronen Verarbeitung

Was ist asynchrone Verarbeitung?

Asynchrone Verarbeitung ist eine Technik, bei der ein Programm, während es auf eine Aufgabe wartet, andere Aufgaben gleichzeitig ausführt. Zum Beispiel führt die übliche synchrone Verarbeitung beim Scraping mehrerer Webseiten Anfragen nacheinander für jede Seite aus. Andererseits ermöglicht die asynchrone Verarbeitung das gleichzeitige Ausführen mehrerer Anfragen.

Unterschiede zwischen synchroner und asynchroner Verarbeitung

MerkmaleSynchrone VerarbeitungAsynchrone Verarbeitung
Ausführungsreihenfolge der AufgabenAufgaben nacheinander einzeln ausführenMehrere Aufgaben gleichzeitig bearbeiten
Wartezeit der VerarbeitungEs entsteht WartezeitAndere Verarbeitungen können dazwischen ausgeführt werden
AnwendungsfälleVerarbeitung kleiner AufgabenSituationen, die umfangreiche I/O-Operationen erfordern

Vorteile der asynchronen Verarbeitung

  • Verbesserung der Effizienz: Durch die gleichzeitige Verarbeitung mehrerer Aufgaben können Wartezeiten effektiv genutzt werden.
  • Skalierbarkeit: Ideal zur effizienten Verarbeitung umfangreicher I/O-Operationen.
  • Ressourcenersparnis: Im Vergleich zur Erstellung von Threads oder Prozessen spart es Systemressourcen.

3. Grundlagen der asynchronen Verarbeitung in Python

Implementierungsmethoden für asynchrone Verarbeitung in Python

In Python werden um asynchrone Verarbeitung durchzuführen die Keywords async und await verwendet. Durch die Verwendung dieser beiden kann man asynchrone Tasks knapp beschreiben.

import asyncio

async def say_hello():
    print("Hallo, asynchrone Verarbeitung!")
    await asyncio.sleep(1)
    print("1 Sekunde vergangen!")

asyncio.run(say_hello())
  • async: Definiert eine Funktion als asynchron.
  • await: Pausiert eine asynchrone Task und ermöglicht die Ausführung anderer Tasks.

Das Funktionsprinzip von Coroutines, Tasks und Event Loop

  • Coroutine: Die Ausführungseinheit für asynchrone Tasks. Eine mit async definierte Funktion wird zu einer Coroutine.
  • Task: Ein Wrapper, um Coroutines im Event Loop zu verwalten.
  • Event Loop: Der Python-Engine, der Tasks ausführt und plant.

4. Praktische Beispiele für asynchrone Verarbeitung

Es gibt in Python vielfältige Szenarien, in denen asynchrone Verarbeitung genutzt wird. In diesem Abschnitt erklären wir detailliert die folgenden Beispiele als praktische Anwendungsfälle.

  • Web-Scraping
  • Parallele Verarbeitung von API-Anfragen
  • Asynchrone Verarbeitung von Datenbankoperationen

Web-Scraping (mit aiohttp)

Beim Web-Scraping sendet man Anfragen an zahlreiche Webseiten, um Daten zu sammeln. Durch die Verwendung asynchroner Verarbeitung können mehrere Anfragen gleichzeitig gesendet werden, was die Verarbeitungsgeschwindigkeit verbessert.

Das Folgende ist ein Beispiel für asynchrones Web-Scraping mit aiohttp.

import aiohttp
 import asyncio

 async def fetch_page(session, url):
     async with session.get(url) as response:
         print(f"Abrufen: {url}")
         return await response.text()

 async def main():
     urls = [
         "https://example.com/page1",
         "https://example.com/page2",
         "https://example.com/page3"
     ]

     async with aiohttp.ClientSession() as session:
         tasks = [fetch_page(session, url) for url in urls]
         results = await asyncio.gather(*tasks)
         print("Alle Seiten abgerufen!")

 asyncio.run(main())
  • Schlüsselaspekte:
  • Mit aiohttp.ClientSession effiziente Anfragen realisieren.
  • Mehrere Tasks parallel ausführen mit asyncio.gather.

Parallele Verarbeitung von API-Anfragen

Auch bei API-Anfragen ist asynchrone Verarbeitung effektiv. Das Folgende ist ein Beispiel, bei dem Anfragen parallel an mehrere API-Endpunkte gesendet und die Ergebnisse abgerufen werden.

import aiohttp
 import asyncio

 async def fetch_data(session, endpoint):
     async with session.get(endpoint) as response:
         print(f"Daten anfordern von: {endpoint}")
         return await response.json()

 async def main():
     api_endpoints = [
         "https://api.example.com/data1",
         "https://api.example.com/data2",
         "https://api.example.com/data3"
     ]

     async with aiohttp.ClientSession() as session:
         tasks = [fetch_data(session, endpoint) for endpoint in api_endpoints]
         results = await asyncio.gather(*tasks)
         for i, result in enumerate(results):
             print(f"Daten von Endpunkt {i + 1}: {result}")

 asyncio.run(main())
  • Schlüsselaspekte:
  • Effiziente Datenerfassung von mehreren API-Endpunkten.
  • Verarbeitung von JSON-formatierten Antwortdaten.

Asynchrone Verarbeitung von Datenbankoperationen (Beispiel mit aiomysql)

Durch die Implementierung asynchroner Datenbankoperationen kann eine schnelle Lese- und Schreibgeschwindigkeit für Daten erreicht werden. Das Folgende ist ein Beispiel für asynchrone Datenbankabfragen mit aiomysql.

import aiomysql
 import asyncio

 async def fetch_from_db():
     conn = await aiomysql.connect(
         host="localhost",
         port=3306,
         user="root",
         password="password",
         db="test_db"
     )
     async with conn.cursor() as cursor:
         await cursor.execute("SELECT * FROM users")
         result = await cursor.fetchall()
         print("Daten aus der Datenbank:", result)
     conn.close()

 asyncio.run(fetch_from_db())
  • Schlüsselaspekte:
  • Asynchrone Abfragen ausführen, um Daten effizient zu erhalten.
  • Auch effektiv bei der gleichzeitigen Verarbeitung mehrerer Abfragen.

5. Hinweise beim Einsatz der asynchronen Verarbeitung

Die asynchrone Verarbeitung ist ein sehr mächtiges Tool, aber wenn sie nicht richtig verwendet wird, können unerwartete Probleme auftreten. In diesem Abschnitt erklären wir die Punkte, auf die beim Einsatz der asynchronen Verarbeitung zu achten ist, und wie man diese vermeiden kann.

Vermeidung von Deadlocks

Ein Deadlock ist ein Phänomen, das auftritt, wenn mehrere Tasks gegenseitig auf Ressourcen warten. Beim Einsatz der asynchronen Verarbeitung muss die Reihenfolge der Tasks und der Zeitpunkt des Erwerbs von Ressourcen angemessen verwaltet werden.Beispiel: Ein Fall, in dem ein Deadlock auftritt

import asyncio

lock = asyncio.Lock()

async def task1():
    async with lock:
        print("Task1 acquired the lock")
        await asyncio.sleep(1)
        print("Task1 released the lock")

async def task2():
    async with lock:
        print("Task2 acquired the lock")
        await asyncio.sleep(1)
        print("Task2 released the lock")

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

asyncio.run(main())

Maßnahmen zur Vermeidung von Deadlocks

  • Die von den Tasks benötigten Ressourcen klar definieren und in der gleichen Reihenfolge erwerben.
  • asyncio.TimeoutError verwenden, um einen Timeout für den Erwerb von Ressourcen festzulegen.

Vermeidung von Race Conditions

In der asynchronen Verarbeitung kann, wenn mehrere Tasks auf dieselbe Ressource zugreifen, eine „Race Condition“ auftreten, bei der die Datenintegrität beeinträchtigt wird.Beispiel: Ein Fall, in dem eine Race Condition auftritt

import asyncio

counter = 0

async def increment():
    global counter
    for _ in range(1000):
        counter += 1

async def main():
    await asyncio.gather(increment(), increment())
    print(f"Final counter value: {counter}")

asyncio.run(main())

Im obigen Beispiel kann der Wert von counter nicht wie erwartet ausfallen.Methoden zur Vermeidung von Race Conditions

  • Verwendung von Locks: asyncio.Lock verwenden, um den gleichzeitigen Zugriff auf Ressourcen zu kontrollieren.
import asyncio

counter = 0
lock = asyncio.Lock()

async def increment():
    global counter
    async with lock:
        for _ in range(1000):
            counter += 1

async def main():
    await asyncio.gather(increment(), increment())
    print(f"Final counter value: {counter}")

asyncio.run(main())

Die Wichtigkeit der Fehlerbehandlung

In der asynchronen Verarbeitung können Netzwerkfehler oder Timeout-Fehler auftreten. Wenn diese Fehler nicht angemessen behandelt werden, kann das zu unerwartetem Verhalten des gesamten Programms führen.Beispiel: Implementierung der Fehlerbehandlung

import asyncio
import aiohttp

async def fetch_url(session, url):
    try:
        async with session.get(url, timeout=5) as response:
            return await response.text()
    except asyncio.TimeoutError:
        print(f"Timeout error while accessing {url}")
    except aiohttp.ClientError as e:
        print(f"HTTP error: {e}")

async def main():
    urls = ["https://example.com", "https://invalid-url"]
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        await asyncio.gather(*tasks)

asyncio.run(main())

Punkte zur Fehlerbehandlung

  • Die vorhersehbaren Fehler identifizieren und entsprechende Behandlungen beschreiben.
  • In der Ausnahmetbehandlung Logs hinterlassen, um beim Troubleshooting zu helfen.

Fälle, in denen asynchrone Verarbeitung ungeeignet ist

Die asynchrone Verarbeitung ist nicht in allen Situationen effektiv. Insbesondere in den folgenden Fällen ist sie ungeeignet.

  1. CPU-intensive Tasks:
  • Prozesse mit hoher CPU-Belastung wie Bildverarbeitung oder das Training von Machine-Learning-Modellen eignen sich besser für concurrent.futures oder multiprocessing als für asynchrone Verarbeitung.
  1. Kleine Tasks:
  • Wenn der Overhead für die Initialisierung der asynchronen Verarbeitung die Verarbeitungszeit übersteigt, ist synchrone Verarbeitung effizienter.

Ressourcenmanagement und Optimierung

Da in der asynchronen Verarbeitung viele Tasks gleichzeitig ausgeführt werden, kann der Verbrauch von Speicher und CPU stark ansteigen. Achten Sie auf die folgenden Punkte, um Ressourcen zu managen.

  • Begrenzung der Anzahl gleichzeitiger Tasks:
    asyncio.Semaphore verwenden, um die Anzahl der gleichzeitig ausgeführten Tasks zu begrenzen.
import asyncio

semaphore = asyncio.Semaphore(5)

async def limited_task(task_id):
    async with semaphore:
        print(f"Running task {task_id}")
        await asyncio.sleep(1)

async def main():
    tasks = [limited_task(i) for i in range(20)]
    await asyncio.gather(*tasks)

asyncio.run(main())
  • Überwachung:
    Ein System einführen, um die Anzahl laufender Tasks und den Speicherverbrauch regelmäßig zu überwachen.

6. Fortgeschrittene Themen zur asynchronen Verarbeitung

Nach dem Verständnis der Grundlagen der asynchronen Verarbeitung ermöglicht das Studium ihrer Anwendungen und Vergleiche mit anderen Technologien eine tiefere Nutzung. Dieser Abschnitt behandelt Vergleiche mit asynchronen Verarbeitungstechniken außerhalb von Python sowie praktische Anwendungsfälle.

Vergleich mit asynchronen Verarbeitungstechniken außerhalb von Python

Auch in anderen Programmiersprachen wird asynchrone Verarbeitung weit verbreitet genutzt. Wir vergleichen besonders beliebte Technologien mit Python und betrachten ihre jeweiligen Merkmale.

Node.js

Node.js ist eine JavaScript-Runtime-Umgebung, die asynchrone Verarbeitung als Stärke hat und asynchrone I/O-Operationen effizient verarbeitet.

MerkmalePythonNode.js
EinsatzbereicheDatenanalyse, KI, WebentwicklungWebserver, Echtzeit-Anwendungen
Realisierung der asynchronen Verarbeitungasyncio-Modul, async/awaitCallbacks, Promise, async/await
Leistung (I/O-Verarbeitung)Hoch, aber etwas unterlegen gegenüber Node.jsOptimiert für asynchrone I/O-Verarbeitung
LernaufwandEtwas hochVergleichsweise niedrig

Go

Go (Golang) realisiert asynchrone Verarbeitung mit Hilfe von Goroutines, die leichte Threads sind.

MerkmalePythonGo
EinsatzbereicheAllgemeine ProgrammierungServer, Cloud-Entwicklung
Realisierung der asynchronen Verarbeitungasyncio-Modul, async/awaitGoroutines, Channels
Leistung (Parallele Verarbeitung)Hoch, aber für CPU-intensive Aufgaben ist asynchron nicht idealZeigt hervorragende Leistung in paralleler Verarbeitung
LernaufwandMittelVergleichsweise niedrig

Vorteile von Python und Einsatzbereiche

  • Vielseitigkeit: Python kann nicht nur für Webentwicklung, sondern auch für Datenanalyse, maschinelles Lernen und viele andere Anwendungen verwendet werden.
  • Reiches Bibliotheksangebot: Durch das Python-Ökosystem (z. B. asyncio, aiohttp) können komplexe asynchrone Verarbeitungen einfach umgesetzt werden.

Anwendungsszenarien für asynchrone Verarbeitung

Durch die Nutzung asynchroner Verarbeitung können effiziente Programme in den folgenden Szenarien erstellt werden.

Serverseitige Entwicklung

Durch die Nutzung asynchroner Verarbeitung können hochbelastete Serveranwendungen effizient erstellt werden. Zum Beispiel ist FastAPI ein Python-Web-Framework, das auf asynchronem I/O basiert, und bietet folgende Vorteile.

  • Schnelle API-Antworten: Erreicht hohe Parallelität und verarbeitet viele Anfragen effizient.
  • Kurzer asynchroner Code: Kann mit async/await einfach beschrieben werden.
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"message": "Hello, FastAPI!"}

Mikroservices

In der Mikroservice-Architektur arbeiten mehrere kleine Dienste zusammen. Die Nutzung asynchroner Verarbeitung bringt folgende Effekte.

  • Effizienz der Kommunikation zwischen Diensten: Erreicht niedrige Latenz durch asynchrone HTTP-Anfragen oder Message Queues.
  • Verbesserte Skalierbarkeit: Flexible Ressourcenverwaltung pro Dienst.

Echtzeitsysteme

In Echtzeitsystemen wie Chat-Apps oder Online-Spielen ermöglicht asynchrone Verarbeitung eine reibungslose Datenaktualisierung. Zum Beispiel kann die websockets-Bibliothek für asynchrone WebSocket-Kommunikation verwendet werden.

import asyncio
import websockets

async def echo(websocket, path):
    async for message in websocket:
        await websocket.send(f"Echo: {message}")

start_server = websockets.serve(echo, "localhost", 8765)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

Nächste Schritte zum Lernen der asynchronen Verarbeitung

Um asynchrone Verarbeitung tiefer zu verstehen, sollten folgende Ressourcen und Themen gelernt werden.

  1. Fortgeschrittene asynchrone Muster:
  • Implementierung von Task-Cancel und Timeouts.
  • Low-Level-APIs von asyncio (z. B. Future oder benutzerdefinierte Event Loops).
  1. Nutzung von Bibliotheken:
  • Bibliotheken für asynchrone I/O (z. B. aiohttp, aiomysql, asyncpg).
  • Asynchrone Web-Frameworks (z. B. FastAPI, Sanic).
  1. Kombination mit verteilter Verarbeitung:
  • Durch die Kombination von asynchroner und verteilter Verarbeitung können noch skalierbarere Systeme erstellt werden.

7. Zusammenfassung

Wir haben die asynchrone Verarbeitung in Python von den Grundlagen bis zu fortgeschrittenen Anwendungen umfassend erläutert. In diesem Abschnitt fassen wir den bisherigen Inhalt zusammen und listen die Punkte auf, die für die effektive Nutzung der asynchronen Verarbeitung wichtig sind. Darüber hinaus schlagen wir Schritte für das nächste Lernen vor.

Übersicht über die asynchrone Verarbeitung

Die asynchrone Verarbeitung ist eine Technik zur effizienten parallelen Ausführung mehrerer Tasks. Besonders nützlich in Szenarien mit vielen I/O-Operationen, mit den folgenden Merkmalen.

  • Effiziente Task-Verarbeitung: Die Wartezeiten für Verarbeitung werden effektiv für andere Tasks genutzt.
  • Verbesserte Skalierbarkeit: Viele Anfragen können effizient verarbeitet werden.

Die wichtigsten Punkte, die in diesem Artikel erläutert wurden

  1. Grundlagen der asynchronen Verarbeitung
  • Unterschied zwischen synchroner und asynchroner Verarbeitung.
  • async und await für die grundlegende Syntax asynchroner Tasks.
  1. Praktische Beispiele für asynchrone Verarbeitung
  • Effiziente Parallelverarbeitung von Web-Scraping oder API-Anfragen asynchron.
  • Schnelle Datenverarbeitung durch Asynchronisierung von Datenbankoperationen.
  1. Achtungspunkte und Herausforderungen
  • Design zur Vermeidung von Risiken wie Deadlocks oder Race Conditions.
  • Angemessene Fehlerbehandlung und Ressourcenmanagement.
  1. Fortgeschrittene Nutzungsmethoden
  • Vergleich mit anderen asynchronen Technologien (Node.js, Go usw.).
  • Anwendungsbeispiele auf Server-Seite oder in Echtzeit-Anwendungen.

Nächste Schritte zum Lernen der asynchronen Verarbeitung

Um die asynchrone Verarbeitung tiefer zu verstehen, empfehlen wir folgendes zusätzliches Lernen.

  1. Nutzung von Bibliotheken
  • Praktische Anwendung mit asynchronen Bibliotheken wie aiohttp, aiomysql, asyncpg usw.
  • Entwicklung von Web-Anwendungen mit asynchronen Web-Frameworks (z. B. FastAPI, Sanic).
  1. Fortgeschrittene Designmuster
  • Nutzung von Task-Cancelation, Ausnahmebehandlung und asynchronen Queues.
  • Low-Level-Design mit benutzerdefinierten Event-Loops von asyncio.
  1. Aufbau praktischer Projekte
  • Erstellen eines kleinen asynchronen Programms zur Überprüfung der Funktionalität.
  • Herausforderung an Projekte, die reale Probleme lösen (z. B. Beschleunigung von APIs, Echtzeit-Kommunikation).

8. FAQ

Zum Schluss fassen wir häufig gestellte Fragen und deren Antworten zu asynchroner Verarbeitung in Python zusammen.

Q1: Was ist der Unterschied zwischen asynchroner Verarbeitung und Multithreading?

Antwort:
Die asynchrone Verarbeitung führt mehrere Aufgaben innerhalb eines einzelnen Threads effizient abwechselnd aus. Im Gegensatz dazu führt Multithreading mehrere Threads gleichzeitig aus, um Aufgaben parallel zu bearbeiten. Asynchrone Verarbeitung eignet sich für Aufgaben mit vielen I/O-Operationen, während Multithreading für CPU-intensive Aufgaben geeignet ist.

Q2: Gibt es geeignete Ressourcen, um asynchrone Verarbeitung zu lernen?

Antwort:
Die folgenden Ressourcen werden empfohlen.

  • Python-Dokumentation: Der Abschnitt zu asyncio.
  • Bücher speziell zu asynchroner Verarbeitung (z. B. „Python Concurrency with Asyncio“).
  • Online-Tutorials (z. B. Real Python, praktische Videos auf YouTube).

Q3: In welchen Situationen sollte asynchrone Verarbeitung verwendet werden?

Antwort:
Asynchrone Verarbeitung ist in den folgenden Situationen effektiv.

  • Bei der Verarbeitung einer großen Anzahl von Web-Anfragen (z. B. Web-Scraping).
  • In Anwendungen, die Echtzeitkommunikation erfordern (z. B. Chat-Apps).
  • Bei Aufgaben mit viel Wartezeit auf I/O von Datenbanken oder externen APIs.

Q4: Eignet sich asynchrone Verarbeitung für CPU-intensive Aufgaben?

Antwort:
Nein, asynchrone Verarbeitung eignet sich nicht für CPU-intensive Aufgaben. Für solche Aufgaben ist es effektiver, Module wie concurrent.futures oder multiprocessing zu verwenden.

侍エンジニア塾