init at work

This commit is contained in:
Mathias Schneider
2026-03-27 12:58:01 +01:00
parent 352c352056
commit 8d40597732
60 changed files with 16498 additions and 0 deletions
+411
View File
@@ -0,0 +1,411 @@
Hier eine Einführung in „asynchron“ in der Python-Programmierung, für jemanden ohne Vorkenntnisse in diesem Bereich.
---
## 1. Intuitive Vorstellung: Was heißt „asynchron“?
Stell dir vor, du kochst:
- Du stellst Wasser auf den Herd (es braucht Zeit, bis es kocht).
- Während du wartest, schneidest du Gemüse, bereitest Sauce vor usw.
- Du **blockierst nicht** deine Zeit, indem du nur auf den Topf starrst.
**Asynchron** bedeutet in der Programmierung:
Dein Programm kann etwas starten, das länger dauert (z.B. eine Netzwerkabfrage), und während es darauf wartet, **andere Dinge erledigen**, statt „untätig“ zu blockieren.
---
## 2. Grundbegriffe: synchron vs. asynchron
### 2.1 Synchron (blockierend)
Synchroner, „klassischer“ Code:
- Befehl A wird ausgeführt.
- Erst wenn A fertig ist, wird B ausgeführt.
- Wenn A lange wartet (z.B. auf eine Antwort aus dem Internet), **steht das ganze Programm an dieser Stelle still**.
Beispiel (synchron, blockierend):
```python
import time
print("Starte")
time.sleep(5) # wartet 5 Sekunden Programm ist blockiert
print("Fertig")
```
Während `time.sleep(5)` läuft, kann das Programm nichts anderes tun.
### 2.2 Asynchron (nicht-blockierend innerhalb eines Ablaufs)
Asynchroner Code versucht:
- Langsame Operationen (z.B. Netzwerk, Festplatte, Datenbank) so zu starten,
- und während sie „laufen“, andere Aufgaben zu bearbeiten.
In Python machst du das typischerweise mit `async` und `await`.
Sehr vereinfachtes Beispiel:
```python
import asyncio
async def aufgabe(name, dauer):
print(f"{name} gestartet")
await asyncio.sleep(dauer) # nicht-blockierend warten
print(f"{name} fertig")
async def main():
# Zwei Aufgaben (Tasks) gleichzeitig laufen lassen
task1 = asyncio.create_task(aufgabe("A", 2))
task2 = asyncio.create_task(aufgabe("B", 2))
await task1
await task2
asyncio.run(main())
```
Typischer Ablauf:
- „A gestartet“
- „B gestartet“
- (2 Sekunden vergehen)
- „A fertig“
- „B fertig“
Beide Aufgaben „warten gleichzeitig“, und die Zeit überlappt sich.
---
## 3. Wichtige Abgrenzungen: Begriffe, die oft durcheinandergehen
### 3.1 Nebenläufigkeit (Concurrency) vs. Parallelität
- **Nebenläufigkeit (Concurrency)**: Mehrere Aufgaben werden so organisiert, dass sie *scheinbar gleichzeitig* laufen, indem man schnell zwischen ihnen hin- und herschaltet.
- **Parallelität (Parallelism)**: Mehrere Aufgaben laufen *wirklich gleichzeitig* auf mehreren CPU-Kernen.
Asynchrones Programmieren in Python (`asyncio`) ist in erster Linie ein Werkzeug für **Nebenläufigkeit**, nicht zwingend für echte Parallelität.
### 3.2 Threads vs. Async
- **Threads**:
- Betriebssystem-Fäden (OS-Threads).
- Können an verschiedenen CPU-Kernen parallel laufen.
- Schwerer zu testen, zu debuggen (Race Conditions, Deadlocks).
- **Async (z.B. asyncio in Python)**:
- Läuft typischerweise in **einem** Thread.
- Nutzt einen **Event Loop**, um zwischen Aufgaben zu wechseln, wenn sie gerade warten.
- Sehr gut geeignet, wenn viele Aufgaben hauptsächlich **I/O-lastig** sind (Netzwerk, Dateien).
**Kurz:**
- Viele Netzwerk-Anfragen gleichzeitig? → Async kann ideal sein.
- Viel CPU-Berechnung (z.B. Bildverarbeitung)? → Threads oder Prozesse (Multiprocessing) sind oft sinnvoller.
### 3.3 Blocking vs. Non-blocking I/O
- **Blockierend**: „Lies aus dem Netzwerk“ der Code bleibt stehen, bis Daten da sind.
- **Non-blockierend**: „Lies aus dem Netzwerk, aber wenn gerade nichts da ist, mach solange andere Aufgaben.“
Asynchrones Python nutzt non-blocking I/O und einen Event Loop, um viele solcher Operationen gleichzeitig zu verwalten.
---
## 4. Asynchron in Python konkret: `asyncio`, `async`, `await`
### 4.1 Historischer Kontext
- Vor Python 3.4 gab es `asyncio` nur als externes Paket.
- Ab Python 3.5 wurden die Schlüsselwörter `async` und `await` eingeführt und haben das Arbeiten mit Async deutlich angenehmer gemacht.
### 4.2 Zentrale Begriffe
- **Coroutine**: eine Funktion, die „angehalten“ und später fortgesetzt werden kann. In Python: definiert mit `async def`.
- **Event Loop**: eine Schleife, die:
- Aufgaben plant,
- sie laufen lässt, bis sie warten müssen (z.B. auf I/O),
- dann anderen Aufgaben CPU-Zeit gibt.
- **Task**: eine geplante Coroutine, die vom Event Loop verwaltet wird.
- **Future**: ein Platzhalter für ein Ergebnis, das noch nicht fertig ist.
### 4.3 Einfaches Beispiel: Event Loop und Coroutines
```python
import asyncio
async def hallo():
print("Hallo...")
await asyncio.sleep(1) # simuliert I/O-Wartezeit
print("...Welt!")
async def main():
await hallo()
asyncio.run(main())
```
- `hallo()` ist eine Coroutine.
- `await asyncio.sleep(1)` bedeutet: „warte 1 Sekunde, aber blockiere nicht den Event Loop“.
---
## 5. Welche Probleme werden durch asynchrones Programmieren gelöst?
### 5.1 Viele gleichzeitige I/O-Aufgaben
Typische Beispiele:
- Webserver, die viele gleichzeitige HTTP-Anfragen beantworten.
- Web-Scraper oder Clients, die viele HTTP-Anfragen an andere Server stellen.
- Chat-Server, WebSockets, Streaming.
- Programme, die gleichzeitig:
- Dateien lesen/schreiben,
- mit einer Datenbank kommunizieren,
- HTTP-Anfragen senden.
**Synchroner Ansatz**:
Jede Anfrage blockiert einen Thread/Prozess, solange sie auf Antwort wartet → sehr viele Threads/Prozesse nötig.
**Asynchroner Ansatz**:
Ein Event Loop verwaltet viele tausend Verbindungen in einem oder wenigen Threads, indem er immer dort weiterarbeitet, wo gerade Daten verfügbar sind.
### 5.2 Bessere Ressourcennutzung bei I/O-lastigen Programmen
Wenn dein Programm hauptsächlich:
- Daten lädt (HTTP, DB),
- auf Antworten wartet,
- nicht viel rechnet,
dann ist asynchroner Code oft **effizienter** (weniger Overhead, weniger Threads, bessere Skalierung).
### 5.3 Responsivere Anwendungen (z.B. GUIs)
In grafischen Anwendungen (oder auch CLI-Tools) willst du:
- Nicht, dass die Oberfläche „einfriert“, während eine Anfrage ans Internet läuft.
- Stattdessen nutzt du asynchrone oder nebenläufige Mechanismen, damit der Haupt-Thread weiterhin Eingaben entgegennimmt.
---
## 6. Praxisnahe Beispiele
### 6.1 Vergleich: synchron vs. asynchron HTTP-Anfragen
#### Synchron: nacheinander mit `requests`
```python
import requests
urls = [
"https://example.com",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
def fetch(url):
print(f"Rufe {url} ab...")
response = requests.get(url)
print(f"{url}: Status {response.status_code}")
def main():
for url in urls:
fetch(url)
if __name__ == "__main__":
main()
```
- Jede Anfrage wartet, bis sie fertig ist.
- Gesamtzeit ≈ Summe aller Wartezeiten.
#### Asynchron: gleichzeitig mit `aiohttp` und `asyncio`
```python
import asyncio
import aiohttp
urls = [
"https://example.com",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
async def fetch(session, url):
print(f"Rufe {url} ab...")
async with session.get(url) as response:
print(f"{url}: Status {response.status}")
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks) # starte alle gleichzeitig
if __name__ == "__main__":
asyncio.run(main())
```
- Alle Anfragen werden „gleichzeitig“ gestartet.
- Gesamtzeit ≈ maximale Einzeldauer, nicht Summe.
### 6.2 Viele „Schlaf-Aufgaben“ parallel (Simulation von I/O)
```python
import asyncio
import random
async def simulierte_io_aufgabe(n):
dauer = random.uniform(0.5, 2.0)
print(f"Aufgabe {n} startet, Dauer ~{dauer:.2f}s")
await asyncio.sleep(dauer)
print(f"Aufgabe {n} fertig")
async def main():
tasks = [simulierte_io_aufgabe(i) for i in range(5)]
await asyncio.gather(*tasks)
asyncio.run(main())
```
Output (ähnlich):
- Mehrere Aufgaben starten schnell hintereinander.
- Sie enden in anderer Reihenfolge, je nach Dauer.
- Die Gesamtzeit liegt in etwa bei der **längsten** Wartezeit, nicht bei der Summe aller.
---
## 7. Typische Herausforderungen und Stolperfallen
### 7.1 Denken in „async“ ist ungewohnt
Für Einsteiger:
- Man kann `await` **nur** in `async def`-Funktionen verwenden.
- Asynchrone Funktionen verhalten sich anders als normale:
```python
async def foo():
return 42
# Aufruf:
result = foo() # das ist KEINE 42, sondern eine Coroutine!
```
Du musst sie ausführen:
```python
import asyncio
async def foo():
return 42
async def main():
result = await foo()
print(result)
asyncio.run(main())
```
### 7.2 Blockierender Code in asynchronem Kontext
Problem:
- Du hast eine `async`-Funktion, benutzt darin aber eine **blockierende** Bibliothek (z.B. `requests`, `time.sleep`).
- Dann blockierst du trotzdem den Event Loop, obwohl du „async“ verwendest.
Beispiel (so besser nicht):
```python
import asyncio
import time
async def schlecht():
print("Blockiere Event Loop...")
time.sleep(5) # blockiert den Event Loop komplett!
print("Weiter geht's")
asyncio.run(schlecht())
```
Lösung:
- Entweder eine **asynchrone Alternative** verwenden (z.B. `aiohttp` statt `requests`).
- Oder blockierende Funktion in einem Thread/Prozess auslagern (z.B. `asyncio.to_thread`).
```python
import asyncio
import time
def blockierende_funktion():
time.sleep(5)
return "fertig"
async def gut():
print("Starte blockierende Funktion in Thread...")
result = await asyncio.to_thread(blockierende_funktion)
print("Ergebnis:", result)
asyncio.run(gut())
```
### 7.3 Debugging und Fehlersuche
- Fehler in asynchronen Programmen können schwerer nachzuvollziehen sein.
- Stack-Traces sehen anders aus, weil Coroutines, Tasks und der Event Loop beteiligt sind.
- Es kann passieren, dass Tasks „stillschweigend“ fehlschlagen, wenn man sie nicht korrekt awaited oder Fehler nicht abfängt.
### 7.4 Testen von asynchronem Code
- Unit-Tests brauchen meist auch einen Event Loop.
- Viele Testframeworks bieten dafür Mechanismen (`pytest` mit `pytest-asyncio`).
Beispiel mit `pytest-asyncio`:
```python
# test_example.py
import pytest
import asyncio
async def verdoppeln(x):
await asyncio.sleep(0.1)
return x * 2
@pytest.mark.asyncio
async def test_verdoppeln():
assert await verdoppeln(21) == 42
```
---
## 8. Wann lohnt sich asynchrones Programmieren (und wann nicht)?
**Sinnvoll bei:**
- Web-APIs, Microservices, Webserver.
- Chat-Server, WebSocket-Anwendungen.
- Web-Scraping vieler Seiten gleichzeitig.
- I/O-lastigen Programmen mit vielen Netzwerk- oder Datenbankzugriffen.
**Weniger sinnvoll bei:**
- Reinen CPU-lastigen Aufgaben (z.B. numerische Berechnungen, Bildverarbeitung).
- Hier helfen eher: mehrere Prozesse (`multiprocessing`) oder spezialisierte Bibliotheken (NumPy, Numba, etc.).
- Kleinen Scripts, die nur wenige, einfache Schritte nacheinander machen da ist synchroner Code oft einfacher und ausreichend.
---
## 9. Zusammenfassung in einfachen Worten
- **Asynchron** in Python bedeutet:
Du kannst zeitaufwändige, I/O-lastige Aufgaben starten und in der Zwischenzeit andere Aufgaben erledigen, statt auf jede einzelne zu warten.
- Die Mechanismen dafür sind:
- `async def` (Coroutines),
- `await` (warten, ohne zu blockieren),
- ein **Event Loop** (z.B. in `asyncio`).
- Es löst besonders gut Probleme mit **vielen gleichzeitigen I/O-Operationen** (Netzwerk, Datenbanken), wie bei Webservern und Web-Scrapern.
- Herausforderungen:
- Umdenken gegenüber normalem, synchronem Code.
- Aufpassen, keine blockierenden Funktionen im Event Loop zu verwenden.
- Debugging und Testen sind etwas komplexer.