Kurz: Manchmal soll ein Programm mehrere Dinge gleichzeitig tun: auf mehrere Webseiten warten, Dateien parallel verarbeiten oder eine Oberfläche bedienbar halten. Python bietet dafür drei Wege.
Teil des Kurses Python
Manchmal soll ein Programm mehrere Dinge gleichzeitig tun: auf mehrere Webseiten warten, Dateien parallel verarbeiten oder eine Oberfläche bedienbar halten. Python bietet dafür drei Wege.
| Werkzeug | Gut für | Prinzip |
|---|---|---|
threading | Warten (Netzwerk, Dateien) | mehrere Abläufe im selben Prozess |
multiprocessing | Rechnen auf mehreren Kernen | mehrere Prozesse |
asyncio | sehr viele wartende Aufgaben | ein Ablauf, der bei Wartezeiten umschaltet |
Hinweis
In CPython sorgt der GIL (Global Interpreter Lock) dafür, dass immer nur ein Thread Python-Code ausführt. Threads beschleunigen daher Warten, aber keine reine Rechenarbeit. Dafür nimmst du Prozesse.
Threads
import threading, time
def arbeiter(name, dauer):
time.sleep(dauer)
print(f"{name} fertig")
start = time.perf_counter()
t1 = threading.Thread(target=arbeiter, args=("A", 0.3))
t2 = threading.Thread(target=arbeiter, args=("B", 0.1))
t1.start(); t2.start()
t1.join(); t2.join() # warten, bis beide fertig sind
print("zusammen unter 0.5 s:", time.perf_counter() - start < 0.5)Ausgabe:
B fertig
A fertig
zusammen unter 0.5 s: TrueBeide Wartezeiten laufen gleichzeitig; die Gesamtzeit ist etwa so lang wie die längere.
Gemeinsame Daten schützen
Wenn mehrere Threads dieselbe Variable ändern, kann es zu Wettlaufsituationen kommen. Ein Lock sorgt dafür, dass nur einer gleichzeitig ändert:
import threading
zaehler = 0
lock = threading.Lock()
def erhoehe():
global zaehler
for _ in range(10000):
with lock:
zaehler += 1
threads = [threading.Thread(target=erhoehe) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(zaehler)Ausgabe:
40000ThreadPoolExecutor
Bequemer geht es mit einem Pool aus concurrent.futures:
from concurrent.futures import ThreadPoolExecutor
import time
def hole(nr):
time.sleep(0.1) # tut so, als würde eine Seite geladen
return nr * nr
with ThreadPoolExecutor(max_workers=5) as pool:
ergebnisse = list(pool.map(hole, range(1, 6)))
print(ergebnisse)Ausgabe:
[1, 4, 9, 16, 25]Prozesse für Rechenarbeit
Mit ProcessPoolExecutor verteilst du Rechnungen auf mehrere Kerne:
from concurrent.futures import ProcessPoolExecutor
def quadratsumme(n):
return sum(i * i for i in range(n))
if __name__ == "__main__":
with ProcessPoolExecutor() as pool:
print(list(pool.map(quadratsumme, [1000, 2000, 3000])))Ausgabe:
[332833500, 2664667000, 8995500500]Der Schutz if __name__ == "__main__": ist hier Pflicht, damit Kindprozesse das Skript nicht erneut starten.
asyncio
Bei asyncio läuft alles in einem einzigen Ablauf. Eine Funktion mit async def ist eine Coroutine; mit await gibt sie die Kontrolle ab, während sie wartet. So kann der Ablauf in der Zwischenzeit andere Aufgaben bedienen:
import asyncio
async def hole(name, dauer):
await asyncio.sleep(dauer)
return f"{name} nach {dauer} s"
async def main():
ergebnisse = await asyncio.gather(hole("A", 0.3), hole("B", 0.1), hole("C", 0.2))
print(ergebnisse)
asyncio.run(main())Ausgabe:
['A nach 0.3 s', 'B nach 0.1 s', 'C nach 0.2 s']gather startet alle Aufgaben gleichzeitig und liefert die Ergebnisse in der übergebenen Reihenfolge.
Zeitüberschreitung und Abbruch
import asyncio
async def langsam():
await asyncio.sleep(5)
async def main():
try:
await asyncio.wait_for(langsam(), timeout=0.1)
except asyncio.TimeoutError:
print("zu langsam, abgebrochen")
asyncio.run(main())Ausgabe:
zu langsam, abgebrochenWas wann?
- Viele Netzwerkanfragen oder Datenbankabfragen:
asynciooder Threads - Rechenintensive Schleifen:
multiprocessing - Einfache Hintergrundaufgabe: ein Thread
- Bibliotheken wie
aiohttpundhttpxbringen asynchrone Webzugriffe
Merke
- Threads: gleichzeitiges Warten; der GIL bremst reine Rechenarbeit
Lockschützt gemeinsame Daten vor Wettlaufsituationenconcurrent.futuresbietet Pools für Threads und Prozesse- Prozesse nutzen mehrere Kerne
async defundawaitbilden Coroutinen;asyncio.runstartet sie,gatherbündelt
Übungsaufgabe
Schreibe ein Programm, das mit asyncio.gather drei “Downloads” (simuliert mit asyncio.sleep) gleichzeitig ausführt und die Gesamtdauer misst.
Quiz zur Selbstkontrolle
Wofür eignen sich Threads in Python vor allem?
- Für Aufgaben, die viel warten (Netzwerk, Dateien) (richtig)
- Für reine Rechenarbeit auf mehreren Kernen
- Für Typprüfung
- Für Grafik
Was macht await?
- Es wartet auf eine Coroutine und gibt währenddessen die Kontrolle ab (richtig)
- Es startet einen neuen Prozess
- Es löscht eine Variable
- Es beendet das Programm
Wozu dient ein Lock?
- Er verhindert, dass zwei Threads gleichzeitig dieselben Daten ändern (richtig)
- Er verschlüsselt Dateien
- Er beschleunigt Schleifen
- Er sperrt das Netzwerk
Weiter im Kurs
Zurück: Kommandozeilen-Programme mit argparse
Weiter: Guter Stil: PEP 8 und Pythonic
Alle Kapitel: Python im Überblick