add synced notes on IT know how
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user