Zurück zum Inhaltsverzeichnis des Manuskripts verteilte Systeme

1.3 Prozesse und Threads

1.3.1 Programmabarbeitungen

Programme

Die Softwarebausteine verteilter Systeme sind Prozesse und Threads. Beide Begriffe beziehen sich auf Programme. Ein Programm ist eine Sammlung von Befehlen, also von Handlungsanweisungen an einen (eventuell abstrakten) Prozessor. Ein Programm ist statisch. Es hat keinen zeitlichen Anfang und kein zeitliches Ende. Damit kann es beispielsweise nicht vom Time Sharing eines Betriebssystems unterbrochen werden. Was durch ein Time Sharing unterbrochen werden kann, ist die Abarbeitung eines Programms.

Prozesse (Tasks)

Die Abarbeitung eines Programms durch einen Prozessor (oder durch Prozessoren) wird als Prozess bezeichnet. Bei manchen Betriebssystemen, wie zum Beispiel bei den Windows-Systemen von Microsoft, wird stattdessen der Begriff Task vorgezogen. Im Gegensatz zu einem Programm ist ein Prozess dynamisch, das heißt, dass er eine Lebensdauer hat. Jede Abarbeitung eines Programms stellt einen eigenen Prozess dar.

Es ist Aufgabe des jeweiligen Betriebssystems, Prozesse zu verwalten. Zu jedem Prozess gehören mehrere, funktional verschiedene Hauptspeicherbereiche, die als Regions bezeichnet werden:

Zusammen werden die Regions als Adressraum des Prozesses (Hauptspeicherabbild, Core Image) bezeichnet. Manche Betriebssysteme, wie zum Beispiel UNIX/Linux, erlauben Prozessen zusätzlich die Einrichtung von Shared Memory Regions, mit deren Hilfe sie miteinander kommunizieren können. Im Abschnitt 1.4.2 (Kommunikationstechniken) wird darauf noch einmal Bezug genommen.

Threads

Wird ein Programm abgearbeitet, dann entsteht dadurch eine bestimmte Befehlsreihenfolge, eine Art Weg durch das Programm. Das Zurücklegen dieses Weges durch den Rechner heißt

Ein solcher Thread ist Teil des zugehörigen Prozesses. In der Frühzeit der Datenverarbeitung gab es bei Programmabarbeitungen zu jedem Prozess immer nur genau einen Thread. Moderne Softwaresysteme, wie zum Beispiel die Virtuelle Java-Maschine (JVM), erlauben es, dass ein Thread einen weiteren, vom ersten unabhängigen, erzeugen kann, der dann nebenläufig zum ursprünglichen ebenfalls Befehle desselben Programms abarbeitet. Moderne Betriebssysteme und moderne Rechnerarchitekturen unterstützen dieses Konzept, so dass heute ein Prozess mindestens einen Thread enthält.

Der Weg durch ein Programm, den ein Thread zurücklegt, wird durch einen Befehlszähler (Program Counter) gesteuert. Dieser Befehlszähler ist ein spezielles Register des jeweiligen Prozessors. Es enthält die Hauptspeicheradresse des Befehls, der als nächster auszuführen ist. Anschaulich zeigt das Register auf diesen Befehl. Wird ein Prozess durch ein Time Sharing unterbrochen, dann wird unter anderem in einer zum Prozess gehörenden Variablen eine Kopie des aktuellen Stands des Befehlszählers, also eine Art Wegmarkierung, abgelegt. Wird dem Prozess zu einem späteren Zeitpunkt wieder Rechenzeit zugeteilt, dann wird diese Kopie des Befehlszählers auf den Befehlszähler zurückgeschrieben. Der Prozess, genauer gesagt, der unterbrochene Thread, wird dadurch an der Unterbrechungsstelle fortgesetzt. Das folgende Bild veranschaulicht diesen Sachverhalt:

Bild: Program Counter bei einem Thread

Werden in einem Prozess mehrere Variablen verwendet, um jeweils eine Kopie des Befehlszählers aufzunehmen, dann können mehrere voneinander unabhängige Abarbeitungswege durch das zugrundeliegende Programm beschritten werden, das heißt, dass zu einem solchen Prozess mehrere voneinander unabhängige Threads gehören:

Bild: Program Counter bei mehreren Threads

Die Abarbeitung eines Programms beginnt immer mit genau einem Thread. Dieser kann weitere Threads starten, die selbst wieder Threads starten können usw. Mit dem Thread-Konzept ist ein Prozess eine Art Hülle oder Ablaufumgebung für seine Threads.

Adressraum der Threads eines Prozesses

Die Threads eines Prozesses arbeiten sich durch ein und dasselbe Programm. Das heißt, dass sich alle Threads eines Prozesses dessen Program Region teilen, und sie teilen sich auch dessen Data Region. Diese ist dadurch ein gemeinsamer Speicherbereich, ein Shared Memory, für diese Threads und kann als Kommunikationsmittel zwischen ihnen dienen. Was die Threads nicht miteinander teilen können, ist der Stack, denn dieser enthält unter anderem die Rücksprungadressen der Unterprogrammaufrufe. Deshalb wird im Prozess für jeden seiner Threads ein eigener Stack geführt.

Nebenläufigkeit der Threads

Die Threads eines Prozesses sind voneinander unabhängig und bereits durch das Time Sharing des Betriebs- oder Laufzeitsystems nebenläufig, und sie sind ohne besonderes Zutun nicht synchronisiert. Das kann zu Zugriffskonflikten führen. Man denke an das nebenläufige Beschreiben einer Variablen. Es ist Aufgabe der Programmierung, Synchronisationsfehler zu vermeiden. Elementare Hilfsmittel dafür werden im Abschnitt 1.3.3 (Threadsicherheit) vorgestellt.

Lebensdauer eines Threads

Ein Thread ist dynamisch, was bedeutet, dass er eine Lebensdauer hat. Sein Leben beginnt mit seiner Erzeugung, und er lebt solange, bis sein letzter Programmbefehl abgearbeitet ist. Die Lebensdauer eines Prozesses beginnt mit der Erzeugung seines ersten Threads und endet, sobald sein letzter Thread beendet ist.

Threadzustände

Threads durchlaufen während ihrer Lebenszeit eine Reihe definierter Zustände. Typisch sind die folgenden:

Das folgende Bild zeigt für den Fall eines Prozesses mit genau einem Thread die für diesen Thread möglichen Zustandsübergänge:

Bild: Zustandsübergänge

Der ganz linke Pfeil soll andeuten, dass der Thread erzeugt, und der ganz rechte, dass sein Adressraum freigegeben wird. Wenn ein Prozess mehrere Threads enthält, dann kann es passieren, dass ein Thread im Zustand Running ist und einen Systemaufruf startet, mit dem der gesamte Prozess mit allen seinen Threads, egal in welchen Zustand diese jeweils sind, beendet wird. Bei einem Prozess mit mehreren Threads muss deshalb bei der grafischen Darstellung der Thread-Zustandsübergänge von jedem Zustand außer Dead ein Übergang zum Zustand Dead vorhanden sein.

Prozess- und Threadverwaltung

Prozesse sind ein Betriebssystemkonzept. Sie werden vom Betriebssystem erzeugt und verwaltet. Threads dagegen können ein Betriebssystemkonzept oder ein Konzept einer Anwendungssoftware, wie zum Beispiel der Virtuellen Java-Maschine, sein. Sind sie ein Betriebssystemkonzept, dann werden sie von diesem erzeugt und verwaltet.

Häufig jedoch, und Java-Programme sind dafür ein Beispiel, soll eine threadorientierte Anwendung auf allen möglichen Betriebssystemen ablauffähig sein, auch auf solchen, die kein Threadkonzept zur Verfügung stellen. In diesen Fällen werden die Threads in der Anwendung realisiert. Ihre Verwaltung ist dann eine Aufgabe des zugehörigen Laufzeitsystems. Bei Java ist dies das Java Runtime Environment (JRE), das die virtuelle Java-Maschine (JVM) enthält. Es verwaltet die Threads, die aus einem Java-Programm hervorgehen. Das heißt, dass es auch das Scheduling, die Zuteilung von Rechenzeit, für diese Threads, übernimmt. Das Betriebssystem hat dann keine Information über die Threads einer virtuellen Java-Maschine. Es nimmt eine JVM als Prozess wahr und verwaltet sie als solchen. Teilt der Scheduler des Betriebssystems einer JVM Rechenzeit zu, dann wird dadurch der Scheduler der JVM aktiv. Er entscheidet, welcher der Threads tatsächlich Rechenzeit erhält.



Zurück zum Inhaltsverzeichnis des Manuskripts verteilte Systeme