Python Multithreading GIL: Threads & Nebenläufigkeit einfach erklärt
Multithreading in Python verstehen: Wie Threads, das threading-Modul und der GIL zusammenhängen – Nebenläufigkeit einfach erklärt für den IT-Alltag.
Von Marcel Schmidtpeter 13. Sept. 2026
Stell dir vor, du schreibst ein Python-Skript, das fünf Webseiten abrufen soll. Jede Anfrage dauert zwei Sekunden - nacheinander also zehn Sekunden. Mit Threads könntest du alle fünf Anfragen gleichzeitig starten und wärst nach gut zwei Sekunden fertig. Doch wenn du dasselbe mit einer rechenintensiven Aufgabe probierst, passiert trotz Multithreading: nichts. Der Grund dafür heißt GIL - und wie Python, Multithreading und dieses GIL zusammenhängen, schauen wir uns jetzt an.
Was ist ein Thread - und was bedeutet Multithreading?
Ein Thread ist ein eigenständiger Ausführungspfad innerhalb eines Programms. Multithreading bedeutet, mehrere dieser Pfade gleichzeitig laufen zu lassen. Stell dir ein Restaurant vor: Ein Koch (dein Programm) kann entweder nacheinander alle Bestellungen abarbeiten - oder mehrere Köche (Threads) arbeiten gleichzeitig an verschiedenen Bestellungen.
Threads teilen sich den Speicher des Programms. Das macht sie leichtgewichtig, führt aber auch zu Problemen, wenn mehrere Threads gleichzeitig dieselbe Variable verändern wollen.
Was ist das GIL in Python?
Das GIL (Global Interpreter Lock) ist eine Sperre im Python-Interpreter, die dafür sorgt, dass zu jedem Zeitpunkt nur ein einziger Thread Python-Bytecode ausführt. Egal wie viele Threads du startest - wirklich gleichzeitig im Sinne von "rechnen" ist immer nur einer.
Warum gibt es das GIL überhaupt? Python verwaltet Speicher über Reference Counting (jedes Objekt zählt, wie viele Verweise auf es zeigen). Würden mehrere Threads gleichzeitig diese Zähler verändern, gäbe es Chaos im Speicher. Das GIL ist also eine einfache Schutzmaßnahme - und zugleich der größte Bremsklotz bei CPU-lastigen Aufgaben. Details dazu findest du im offiziellen Python-Wiki zum GIL.
Merke: Das GIL bedeutet nicht, dass Threads nutzlos sind. Es bedeutet nur: Threads helfen bei Wartezeiten, nicht bei reiner Rechenarbeit.
I/O-bound vs. CPU-bound: Welche Aufgabe hast du vor dir?
Die Wahl des richtigen Werkzeugs hängt davon ab, ob deine Aufgabe I/O-bound (wartelastig) oder CPU-bound (rechenlastig) ist. Diese Tabelle zeigt den Unterschied auf einen Blick:
Eigenschaft | I/O-bound (wartelastig) | CPU-bound (rechenlastig) |
Beispiel | Netzwerkabruf, Dateien lesen, DB-Abfragen | Bildverarbeitung, Berechnungen, Komprimierung |
Typisches Problem | Programm wartet auf Antwort | Prozessor arbeitet auf Volllast |
Passende Lösung | `threading` oder `asyncio` | `multiprocessing` |
GIL im Weg? | Nein - GIL wird beim Warten freigegeben | Ja - nur ein Thread rechnet gleichzeitig |
Der Trick: Wenn ein Thread auf eine Netzwerkantwort wartet, gibt er das GIL frei. Ein anderer Thread kann in der Zeit arbeiten. Deshalb bringt Multithreading bei Downloads oder Dateizugriffen echten Speed-Gewinn. Die offizielle Dokumentation zum `threading`-Modul erklärt die Details.
Kernregel: Multithreading beschleunigt wartelastige Aufgaben (I/O-bound) deutlich. Bei rechenlastigen Aufgaben (CPU-bound) blockiert das GIL den Speed-Gewinn.
Praxisbeispiel: Downloads parallelisieren
Bei wartelastigen Aufgaben liefert Multithreading sofort messbare Gewinne. Hier ein vollständiges, lauffähiges Beispiel: Drei "Downloads" laufen parallel - statt sechs Sekunden braucht das Skript nur zwei.
import threading
import time
def datei_laden(name, dauer):
print(f"Starte Download: {name}")
time.sleep(dauer) # simuliert die Netzwerk-Wartezeit
print(f"Fertig: {name}")
start = time.perf_counter()
threads = []
for i in range(3):
t = threading.Thread(target=datei_laden, args=(f"datei_{i}.txt", 2))
threads.append(t)
t.start()
# Warten, bis alle Threads fertig sind
for t in threads:
t.join()
gesamt = time.perf_counter() - start
print(f"Gesamtzeit: {gesamt:.2f} Sekunden")Wichtig an diesem Code: `t.join()` blockiert das Hauptprogramm, bis der Thread fertig ist. Ohne `join()` läuft dein Skript weiter, während die Threads noch arbeiten - Ausgaben direkt danach kämen also zu früh.
Wenn du dasselbe mit einer CPU-lastigen Funktion testest (z. B. eine große Zahl berechnen statt `time.sleep`), wirst du kaum bis gar keinen Geschwindigkeitsvorteil messen. Das GIL lässt nur einen Thread gleichzeitig rechnen.
Wann nutzt man Multiprocessing statt Threads?
Multiprocessing startet mehrere eigenständige Python-Prozesse statt Threads. Jeder Prozess hat seinen eigenen Interpreter - und damit ein eigenes GIL. Dadurch können mehrere CPU-Kerne wirklich parallel rechnen. Die Dokumentation zum `multiprocessing`-Modul zeigt den Einstieg.
from multiprocessing import Pool
def quadriere(x):
return x * x
if __name__ == "__main__":
with Pool(processes=4) as pool:
ergebnisse = pool.map(quadriere, range(1000000))
print(f"Fertig: {len(ergebnisse)} Ergebnisse")Der Preis: Prozesse sind schwerer als Threads, und der Datenaustausch zwischen ihnen ist aufwendiger, weil sie keinen gemeinsamen Speicher haben.
Häufige Anfängerfehler
Drei Stolperfallen tauchen bei fast allen auf, die zum ersten Mal mit Python-Multithreading arbeiten.
Fehler 1: Threads für rechenlastige Aufgaben
Viele verteilen eine Schleife mit Millionen Berechnungen auf vier Threads - und wundern sich, dass es nicht schneller wird. Das GIL sorgt dafür, dass sich die Threads quasi die Warteschlange zur CPU teilen. Lösung: `multiprocessing` nutzen.
Fehler 2: Geteilte Variablen ohne Lock
`zaehler += 1` sieht atomar aus, ist es aber nicht. Dahinter stecken drei Schritte: lesen - addieren - zurückschreiben. Zwischen diesen Schritten kann ein anderer Thread dazwischenfunken. Das nennt man Race Condition:
import threading
zaehler = 0
def erhoehen_ungeschuetzt():
global zaehler
for _ in range(100000):
zaehler += 1 # FALSCH: nicht thread-sicher
threads = [threading.Thread(target=erhoehen_ungeschuetzt) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(zaehler) # oft weniger als 200000!Richtig geht es mit einem Lock (einer Sperre, die immer nur ein Thread gleichzeitig besitzen darf):
import threading
lock = threading.Lock()
zaehler = 0
def erhoehen_geschuetzt():
global zaehler
for _ in range(100000):
with lock: # RICHTIG: nur ein Thread gleichzeitig
zaehler += 1
threads = [threading.Thread(target=erhoehen_geschuetzt) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(zaehler) # jetzt immer 200000Fehler 3: Vergessenes join()
Ohne `t.join()` läuft das Hauptprogramm weiter, während die Threads noch arbeiten. Beim Programmende wartet Python zwar auf normale (nicht-daemon) Threads - aber Ergebnisse, die du direkt danach ausgeben willst, sind dann eben noch nicht fertig.
Übungstipp
Nimm das Download-Beispiel von oben und bau es um: Ersetze `time.sleep` durch eine echte Rechenaufgabe, etwa `sum(i * i for i in range(10_000_000))`. Miss die Zeit mit `time.perf_counter()` - einmal mit drei Threads, einmal ohne. Du wirst sehen: praktisch kein Unterschied. Dieses Aha-Erlebnis sitzt danach tiefer als jede Theorie.
FAQ
Was ist das GIL in Python?
Das Global Interpreter Lock ist eine Sperre im Python-Interpreter, die verhindert, dass mehrere Threads gleichzeitig Python-Bytecode ausführen. Es schützt die Speicherverwaltung, bremst aber CPU-lastige Threads aus.
Wann bringt Multithreading in Python etwas?
Bei wartelastigen Aufgaben (I/O-bound) wie Netzwerkabrufen, Dateioperationen oder Datenbankabfragen. Der Thread gibt das GIL beim Warten frei, sodass andere Threads weiterarbeiten können.
Warum ist mein multithreaded Programm nicht schneller?
Weil deine Aufgabe vermutlich CPU-bound ist. Durch das GIL rechnet immer nur ein Thread gleichzeitig - da helfen nur Prozesse (`multiprocessing`) oder eine andere Sprache für den rechenintensiven Teil.
Was ist der Unterschied zwischen threading und multiprocessing?
Threads teilen sich den Speicher eines Prozesses und sind leichtgewichtig, werden aber vom GIL gebremst. Prozesse haben eigenen Speicher und ein eigenes GIL, können dafür echte Parallelität auf mehreren CPU-Kernen nutzen.
Wird das GIL abgeschafft?
Seit Python 3.13 existiert ein experimentelles "free-threaded" Build ohne GIL (PEP 703). Für Produktionscode ist das Standard-Build mit GIL aber weiterhin die Regel - rechne also weiter damit.
Fazit
Multithreading in Python ist ein hervorragendes Werkzeug - aber nur für die richtigen Aufgaben. Merk dir die einfache Regel:
Warten → Threads, Rechnen → Prozesse.
Das GIL ist kein Bug, sondern ein Kompromiss, der die Speicherverwaltung einfach hält. Wer versteht, wann der Lock freigegeben wird und wann nicht, vermeidet die typischen Performance-Fallen gleich zu Beginn.
Wenn du solche Konzepte vertiefen willst - etwa für deine IHK-Abschlussprüfung, ein Uni-Modul oder den Berufseinstieg - schau dir die Nachhilfe-Angebote auf study-it.education an. Dort erklären wir dir Python-Grundlagen wie Exception Handling und Lambda-Funktionen genauso praxisnah wie in diesem Artikel.
Brauchst du jemand, der das mit dir durchgeht?
Statt nochmal lesen: 30 min mit einem Senior-Dev.
Kostenloses Erstgespräch buchen →
Multithreading in Python verstehen: Wie Threads, das threading-Modul und der GIL zusammenhängen – Nebenläufigkeit einfach erklärt für den IT-Alltag.
marcel.schmidtpeter@study-it.education →Mehr Lesestoff
Python HTTP requests senden: GET, POST & mehr mit requests
So sendest du mit der Python-Bibliothek requests GET- und POST-Anfragen an APIs – mit Beispielen für Azubis, Studenten und Berufseinsteiger.
Python JSON verarbeiten mit dem -Modul – einfach erklärt
JSON in Python lesen und schreiben? Das -Modul macht's einfach – von .loads bis .dumps, hier lernst du alles Wichtige.
Python Datenbank verbinden: sqlite3 & SQLAlchemy einfach erklärt
Erfahre, wie du Python per sqlite3 oder SQLAlchemy mit Datenbanken verbindest – mit Praxisbeispielen für Azubis, Studenten & Berufseinsteiger.