Thread (Informatik)
Unter einem Thread [
] (engl. thread = Faden, Strang; auch Aktivitätsträger) versteht man- im Usenet und in vergleichbaren Web-Foren eine Folge von Diskussionsbeiträgen, von denen jeder mit Ausnahme des Ursprungs einen anderen Beitrag beantwortet (durch Bezugnahme mit einem References:-Feld) und die deshalb hierarchisch organisiert sind,
- in der Software-Architektur einen Ausführungsstrang innerhalb eines Prozesses, der nebenläufig zu anderen Threads ausgeführt werden kann.
Die folgenden Erläuterungen beziehen sich auf die zweite Bedeutung.
Auf Betriebssystemen, die Threads unterstützen, ist der Verwaltungsaufwand für Threads üblicherweise geringer als der für Prozesse. Ein wesentlicher Effizienzvorteil von Threads besteht darin, dass im Gegensatz zu Prozessen beim Threadwechsel kein vollständiger Austausch des Prozesskontextes notwendig ist, da ja alle Threads einen gemeinsamen Teil des Prozesskontextes verwenden.

Das Bild zeigt die beschriebenen Zusammenhänge im Detail. Ein Thread teilt sich mit den anderen vorhandenen Threads des zugehörigen Prozesses eine Reihe von Betriebsmitteln, nämlich das Codesegment, das Datensegment und die verwendeten Dateideskriptoren. Allerdings bewahrt jeder Thread seinen eigenen Befehlszähler und seinen eigenen Stack. Durch die gemeinsame Nutzung des Speicherbereichs kann es natürlich auch zu Konflikten kommen. Diese müssen durch den Einsatz von Synchronisationsmechanismen aufgelöst werden. Die Gesamtheit zusammengehöriger Threads inklusive ihrer Betriebsmittel bezeichnet man als Task.
Da Threads, die dem selben Prozess zugeordnet sind, den gleichen Adressraum verwenden, ist eine Kommunikation zwischen diesen Threads von vorneherein möglich (vgl. mit Interprozesskommunikation bei Prozessen).
Threads innerhalb des gleichen Prozesses verwenden voneinander unabhängige Stapel (Stacks), die unterschiedlichen Abschnitten des Adressraums zugeordnet sind. .
Jeder „Programmfaden“ ist für die Ausführung einer bestimmten Aufgabe verantwortlich. Die Ausführungsstränge der Programmfunktionen können damit in überschaubare Einheiten aufgeteilt werden.
Bei den meisten Betriebssystemen kann ein Thread neben dem Zustand inaktiv die Zustände rechnend (engl. running), rechenbereit (engl. ready) und blockiert (engl. waiting) annehmen. Im Zustand rechnend findet die Ausführung von Befehlen auf der CPU statt, bei rechenbereit ist der Thread gestoppt, um einen anderen Thread rechnen zu lassen und bei blockiert wartet der Thread auf ein Ereignis.
Threads in Java
Die Programmiersprache Java ist auf fast allen Plattformen lauffähig und kann als allgemein bekannt angesehen werden. Daher soll Java zur Illustration der Threadverarbeitung herangezogen werden. In Java ist ein Arbeiten mit mehreren Threads von vornherein vorgesehen. Dabei funktioniert das Multithreading auch, wenn das Betriebssystem dieses nicht oder nur mangelhaft unterstützt. Möglich ist das, weil die Virtuelle Maschine des Java die Threadumschaltung einschließlich Stackverwaltung übernehmen kann. In Betriebssystemen mit Threadunterstützung können die Betriebssystemeigenschaften direkt genutzt werden. Die Entscheidung darüber liegt in der Programmierung der Virtuellen Maschine.
In Java gibt es im Basis-Package lang die class Thread. Instanzen von dieser Klasse sind Verwaltungseinheiten der Threads. Eine Instanz von Thread kennt eine beliebige Anwenderklasse, diese muss eine Funktion run() enthalten und vom interface Runnable abgeleitet sein.
Ein Thread wird gestartet mittels Aufruf von thread.start(). Dabei wird die run()-Methode der assoziierten Anwenderinstanz abgearbeitet. Solange run() läuft, ist der Thread nicht inaktiv.
In der Methode run() oder in den von dort gerufenen Methoden kann der Anwender mittels wait() den Thread eine Zeit (in Millisekunden angegeben) oder auch beliebig lange warten lassen. Dieses Warten wird aber mit einem notify() aus einem anderen Thread beendet. Das ist ein wichtiger Mechanismus der Inter-Thread-Kommunikation. wait() und notify() sind Methoden der class Object und auf alle Instanzen von Daten anwendbar. Zueinandergehörige wait() und notify() sind an der selben Instanz (einer Anwenderklasse) zu organisieren, sinnvollerweise werden in dieser Instanz dann auch die Daten übergeben, die ein Thread dem anderen mitteilen möchte.
Die Realisierung von kritischen Abschnitten erfolgt mit synchronized.
In der ersten Version von Java wurden Methoden der class Thread zur Unterbrechung eines Threads von außen, Fortsetzung und Abbruch eingeführt: suspend(), resume() und stop(). Diese Methoden wurden aber recht schnell in Nachfolgeversionen als Deprecated (missbilligt) bezeichnet. In der ausführlichen Begründung wurde ausgeführt, dass ein System unsicher ist, wenn ein Thread von außen angehalten oder abgebrochen werden kann. Die Begründung mit wenigen Worten ist folgende: Ein Thread kann sich möglicherweise in einer Phase eines kritischen Abschnittes befinden und Daten teilweise geändert haben. Wird er angehalten, dann ist der kritische Abschnitt blockiert und deadlocks sind die Folge. Wird er abgebrochen und die Blockierung vom System aufgehoben, dann sind Daten inkonsistent. An dieser Stelle kann ein Laufzeitsystem nicht selbst entscheiden. Ein Anhalten oder Abbruch eines Threads kann nur im Anwenderprogramm selbst gesteuert erfolgen. Dazu muss der Anwender an bestimmten Stellen Bedingungen für Abbruch oder Warten abfragen. Das ist auch meist recht einfach programmierbar, da Threads meist zyklische Aktivitäten erledigen müssen und daher in Schleifen programmiert sind. Dies ist eine wichtige Erfahrung beim Umgang mit Threads.
Threads in UML
Die Unified Modeling Language (UML) ist eine vereinheitlichte grafisch orientierte Ausdrucksform der Zusammenhänge in Softwaresystemen. Das Thema Thread wird in der UML nicht vordergründig behandelt, da eine Funktionsaufteilung zu Threads eher eine Frage der Implementierung ist als eine Frage des Software-Entwurfs ist. Allerdings erheben UML-Systeme den Anspruch der Durchgängigkeit bis zur Implementierung mit Code-Generierung bis zum lauffähigem Programm, was die Beachtung von Thread-Erfordernissen einschließen muss.
Parallele Prozesse werden in UML oft mit Zustandsdiagrammen (Statecharts) dargestellt. In einem Zustandsdiagramm sind innerhalb eines Zustandes interne parallele Teil-Zustandsdiagramme darstellbar. Alle Zustandsdiagramme des Gesamtsystems werden quasiparallel angearbeitet. Die Parallelität wird dadurch erreicht, dass jeder Zustandsübergang atomar kurz sein soll (in der Praxis in wenigen Mikrosekunden bis Millisekunden abgearbeitet ist) und daher das Nacheinander der Abarbeitung als quasiparallel erscheint. Das geht technisch auch mit einem einzigen Thread.
Setzt man UML für schnelle Systeme ein, dann spielt die Frage einer zeitlichen Priorisierung eine Rolle. Aus diesem Grunde muss man die Zustandsdiagramm-Abarbeitung von Instanzen beziehungsweise Klassen verschiedenen Threads des System zuordnen können. Das UML-Werkzeug Rhapsody kennt dazu den Begriff der aktiven Klasse. Jede aktiven Klasse ist einem eigenem Thread zugeordnet. Das bezieht sich aber nur auf die Abarbeitung der Zustandsdiagramme.
Will man in einem System, dass mit UML entworfen ist, Threads bewusst einbringen, beispielsweise für hochzyklische Prozesse, dann muss man Klassen für die Threadverarbeitung, die im jeweilige Laufzeit-System vorhanden sind, in den Anwender-Entwurf einbringen. Benutzt man Java als Implementierungssprache, dann stehen die Klassen Thread, Runnable usw. aus dem Laufzeitsystem des Java dazu zur Verfügung und können unmittelbar genutzt werden. Bei einer anderen Implementierungssprache (oft C oder C++) stehen standardgemäß keine Thread-Verwaltungsklassen zur Verfügung, der Softwareentwurf ist daher an diesen Stellen entweder stark vom Implementierungssystem abhängig oder muss vom Anwender geeignet gekapselt werden.
Die Möglichkeit der Darstellung von Multithread-Problemen mit Zustandsdiagrammen scheint an einigen Stellen passend zu sein, geht allerdings an der eigentlichen Aufgabe von Zustandsdiagrammen vorbei, das Verhalten einer Klasse zu modellieren. Auch gibt es Probleme, weil alle Schritte der Multithread-Verarbeitung in extra Zustandsübergänge aufgelöst werden müssen. Die Umschaltung der Threads innerhalb der Zustandsübergänge ist problematisch beziehungsweise nicht möglich bei Ablauf im selben System-Thread. Damit gibt es im Entwurf Differenzen zum tatsächlichen Verhalten bezüglich Reaktion auf Ereignisse, wenn die Besonderheiten nicht richtig beachtet werden.
Beispiel einer hochzyklischen Abarbeitung in einem Thread:
while(not_abort) //zyklisch bis zum Abbruch von aussen { data.wait(); //der Zyklus beginnt wenn Daten vorliegen dosomething(); dootherthings(); //Abarbeitung mehrerer Dinge if(anystate) { dotheRightThing(); //Abarbeitung ist von Bedingungen abhängig partnerdata.notify(); //andere Threads beachrichtigen } else doOthers(); }
Das kann in UML Inhalt einer Methode in einer Klasse sein. Unter Nutzung von Java ist das die Methode run(), die Klasse instanziiert Runnable und ist einer Instanz von Thread zugeordnet (Assoziation im Klassendiagramm). Die Programmierung ist übersichtlich. In einem Zustandsdiagramm kann das so nicht unmittelbar untergebracht werden, weil der Inhalt der Methode zu wenig atomar ist.
Threads unter UNIX und Windows
folgt noch mit kurzer Benennung einiger wichtiger Funktionsrufe
Siehe auch
- Verklemmung (deadlock)
- Multithreading
- Hyper-Threading
- Kritischer Abschnitt
- Nebenläufigkeit
- Parallelisierung
- Parallelisierbarkeit
- Prozess
- Semaphor
- Synchronisation
- Locking (Sperre)
- Socket
- Server
- Task
- Kommunikation
- Mutex
Literatur
- Peter Ziesche: Nebenläufige & verteilte Programmierung, W3L, 2004, ISBN 3937137041
- Marcus Roming, Joachim Rohde: Assembler - Grundlagen der Programmierung. MITP-Verlag, ISBN 3-8266-0671-X
- Olaf Neuendorf: Windows Multithreading mit C++ und C#. MITP-Verlag, ISBN 3-8266-0989-1
- Heinz Kredel, Akitoshi Yoshida: Thread- und Netzwerk-Programmierung mit Java. Dpunkt Verlag, ISBN 3-89864-133-3
- Rainer Oechsle: Parallele Programmierung mit Java Threads. Hanser Fachbuchverlag, ISBN 3-446-21780-0
- Andrew S. Tanenbaum: Moderne Betriebssysteme. Pearson Studium Verlag, ISBN 3-8273-7019-1
- Hans Joachim Müschenborn: OS/2 Systemprogrammierung. Multitasking – Interprozesskommunikation – Multithreading – DB/2-Integration. tewi-Verlag, ISBN 3-89362-344-2
Weblinks
- Programmierung mit POSIX- und Java-Threads - Pack die Threads in den Bankomat!
- Prozesse und Threads - Hintergrund und aktuelle Entwicklungen - Klon-Debatte
- Forum
- Antworten zu häufig gestellten Fragen aus comp.programming.threads (englisch)
- Frequently Asked Questions, Most FAQ (englisch)
- Diskussion "Writing a scalable server" (englisch)
- The C10K problem (englisch)
- Business logic processing in a socket server - thread pools (englisch)
- Einführung in Pthreads mit C (englisch)
- Vorlesungsfolien zu Pthreads