EMZETT.
Login

Threads (Nebenläufigkeit)

Kurz: Ein Thread ist ein eigenständiger Ausführungspfad innerhalb eines Programms — mehrere Threads können scheinbar oder tatsächlich gleichzeitig laufen und sich dabei Speicher und Ressourcen des Programms teilen.

Genauer: Threads erlauben es z. B., eine rechenintensive Aufgabe im Hintergrund laufen zu lassen, während die Benutzeroberfläche weiter reagiert. Die größte Herausforderung ist, dass mehrere Threads, die gleichzeitig auf dieselben Daten zugreifen, zu Race Conditions führen können (unvorhersehbare Ergebnisse je nach Ausführungsreihenfolge) — dagegen helfen Synchronisationsmechanismen, die den gleichzeitigen Zugriff kontrolliert einschränken.

Im Detail

Eine Race Condition entsteht, wenn zwei Threads dieselbe Variable gleichzeitig lesen UND schreiben, und das Endergebnis davon abhängt, in welcher Reihenfolge das genau passiert — ein klassisches Beispiel:

# zaehler startet bei 0, zwei Threads führen GLEICHZEITIG aus:
# Thread A:                    Thread B:
wert = zaehler                 wert = zaehler       # beide lesen 0
wert = wert + 1                wert = wert + 1      # beide rechnen 0+1=1
zaehler = wert                 zaehler = wert       # beide schreiben 1
 
# Erwartet: zaehler = 2 (zwei Erhöhungen)
# Tatsächlich: zaehler = 1 (eine Erhöhung ging "verloren")

Obwohl jeder einzelne Thread korrekten Code ausführt, geht durch das Verschränken der Ausführungsreihenfolge eine Erhöhung verloren — und das Problem tritt nicht zuverlässig bei jedem Programmlauf auf, was Race Conditions zu den am schwersten zu findenden und reproduzierenden Fehlerarten überhaupt macht.

Die klassische Lösung ist ein Lock (auch Mutex genannt): Ein Thread muss sich den exklusiven Zugriff auf eine kritische Codestelle erst “sichern”, bevor er sie betreten darf — jeder andere Thread muss warten, bis der Lock wieder freigegeben wird:

lock.sperren()
zaehler = zaehler + 1   # nur EIN Thread kann diesen Block gleichzeitig betreten
lock.freigeben()

Locks lösen Race Conditions, bringen aber eigene Risiken mit: Ein Deadlock entsteht, wenn zwei Threads jeweils auf einen Lock warten, den der andere gerade hält — beide bleiben für immer blockiert, weil keiner nachgibt. Ein typischer Auslöser ist, mehrere Locks in unterschiedlicher Reihenfolge zu sperren; die gängige Vermeidungsstrategie ist, in der gesamten Codebasis immer eine feste, konsistente Sperrreihenfolge einzuhalten.

Moderne Sprachen bieten oft höhere Abstraktionen an, die viele dieser Fallstricke von Anfang an vermeiden — etwa unveränderliche (immutable) Datenstrukturen (die keine Race Condition erleiden können, weil sie nie verändert werden), threadsichere Collections, oder ganz andere Nebenläufigkeitsmodelle wie Message-Passing statt geteiltem Speicher.

Siehe auch: Recursion, Algorithms