Site logo
Site logo
Programmieren aus Leidenschaft
Programmieren aus Leidenschaft

Einführung Multithreading


Auf einem Computer passieren wenige Dinge gleichzeitig. Es ist inzwischen möglich Musik zu hören, Dateien zu kopieren und einen Brief zu schreiben und all dies offensichtlich zur gleichen Zeit.

Dieses, auch als Multitasking bezeichnete Verfahren, ist aus dem heutigen Computeralltag nicht mehr wegzudenken. Allerdings ist es ein Irrglauben, würde man annehmen, all diese Dinge geschähen tatsächlich gleichzeitig. In Wirklichkeit wird nur ganz schnell zwischen den verschiedenen Anwendungen hin- und hergeschaltet. So entsteht der Eindruck der Gleichzeitigkeit. Es gibt verschiedene Arten dieses Umschalten zu organisieren und in der Regel wird dies auch vom Betriebssystem gesteuert, so dass man als Benutzer hier keinen Einfluss hat.

Sieht man sich eine laufende Anwendung genauer an, wird man leicht feststellen, dass auch hier verschiedene Vorgänge scheinbar gleichzeitig ablaufen. So kann man problemlos ein Dokument drucken, während man schon an einer überarbeiteten Version schreibt. Eventuell arbeitet im Hintergrund dann noch eine Rechtschreibprüfung. Hier spricht man nicht mehr von Multitasking, sondern von Multithreading.

Ein Thread, was übersetzt soviel wie "Faden" bedeutet, ist ein eigenständiger Arbeitsablauf, der parallel zum Hauptprogramm, dem Prozess, läuft. In den meisten modernen Programmiersprachen können die Entwickler eigenständig Threads für ihre Anwendungen erstellen. Threads sind eine sehr effektive Methode, vor allem Programme mit grafischer Benutzeroberfläche, bedienbar zu halten. Schließlich möchte der Benutzer auch dann weiterhin mit einem Programm arbeiten, wenn im Hintergrund beispielsweise aufwendige Berechnungen durchgeführt werden. Auch bei einer Textverarbeitung möchte man nicht, dass die Eingabe durch die Rechtschreibprüfung unterbrochen wird.

Aber auch auf Thread-Ebene passiert nichts wirklich gleichzeitig. Zwar gibt es inzwischen Computer mit mehr als einer CPU, aber in den meisten Programmen passieren so viele Dinge im Hintergrund, dass kein Thread eine Prozessor für sich allein hat. Die Gleichzeitigkeit ist wieder nur scheinbar existent. In Wirklichkeit werden auch die Threads unterbrochen und zu einem späteren Zeitpunkt fortgesetzt. Wann diese Unterbrechungen geschehen, ist nicht beeinflussbar.

Für einen Software-Entwickler entsteht hier ein breites Spektrum neuer Probleme. Denn sieht man genauer hin, so geschieht in einem Programm viel mehr als vermutet. Eine der einfachsten Funktionen, ist das Hochzählen einer Zahl. In Programmiersprache der C-Familie oftmals abgekürzt als:

i++;

Schreibt man diesen Befehl in seiner ursprünglichen Form, so ist das:

i = i + 1;

Klar zu erkennen wird der Wert von i um den Wert 1 erhöht und dann wieder i zugewiesen. Dabei ist i eine Variable die irgendwo in einem physikalischen Speicher steht. Zerlegen wir diesen Vorgang in einzelne Schritte, so ergibt sich daraus folgender Ablauf:

1. Den Wert von i aus dem Speicher lesen und merken
2. Den gemerkten Wert um 1 erhöhen
3. Den erhöhten Wert wieder in den Speicher schreiben

Wie schon erwähnt kann der Ablauf eines Thread aber jederzeit unterbrochen werden. Was geschieht also wenn der Ablauf zwischen Schritt 2 und Schritt 3 unterbrochen wird? Es passiert nichts, denn sobald dieser Thread fortgesetzt wird, wird auch der um eins erhöhte Wert wieder in seinen Speicherbereich geschrieben.

Was aber wenn zwei Threads "gleichzeitig" versuchen den wert von i zu erhöhen?

Thread 1 liest den Inhalt aus dem Speicher, merkt sich diesen Wert und erhöht ihn um 1. Bevor dieser Thread aber den geänderten Wert wieder speichern kann, wird er von Thread 2 unterbrochen. Die Aufgabe von Thread 2 ist genau die gleiche, aber dieser Thread hat Glück und die Abarbeitung der drei Schritte wird nicht unterbrochen. Nun befindet sich ein erhöhter Wert in der Speicherstelle der Variablen i. Jetzt ist Thread 1 wieder an der Reihe. Der hat nicht mehr viel mehr zu tun, als seinen Wert in die Speicherstelle von i zu schreiben. Damit überschreibt er aber genau den Wert, der schon in der Speicherstelle steht.

Es wird schnell erkennbar, dass der Inhalt von i nur um den Wert 1 erhöht wird, der Vorgang des Aufaddierens aber zwei Mal ausgeführt wurde. Einzig dadurch, dass der Thread 1 unterbrochen wird, bevor er sein Ergebnis speichern kann, wird das Endergebnis verfälscht. Thread 2 nimmt sich den Wert aus der Speicherstellte von i. Das Thread 1 schon einen veränderten Wert bereithält ist unbedeutend, da dieser Wert noch nicht in den Speicher zurück geschrieben wurde.

Natürlich ist es nicht grundlegend verkehrt mit Threads solche Berechnungen durchzuführen, oftmals ist das sogar zwingend notwendig. Gefährlich wird es nur wenn mehrere Threads dieselben “Bausteine“ benutzen.

Diese „Bausteine“, auch als „Resourcen“ bezeichnet, beschränken sind aber nicht nur auf Variablen. Auch Steuerelemente und sogar Datenbanken oder externe Dateien sind Resourcen und sie benötigen beim Einsatz von Multithreading besondere Beachtung.

Wie man eigenen Threads erstellt und was dabei zu beachten ist werden die nächsten Beispiele zeigen.