|
Das Implementieren von Threads in Klassen bringt einige Probleme mit sich. Im Konstruktor kann nach Übernahme der Daten der Thread gestartet werden. Verläßt das Thread-Objekt jedoch seinen Gültigkeitsbereich, wird der Destruktor aufgerufen und das Objekt zerstört. Ist die Funktion, die den eignen Thread darstellt, noch aktiv, so werden auch hier die Daten dem Thread einfach entzogen. Wie man es auch dreht und wendet, die Gültigkeitsbereiche von C-Variablen und C++-Objekten einerseits und von OS/2-Threads andererseits sind nicht identisch. Um diese Gültigkeitsbereiche zur Deckung zu bringen, bedarf es eines sorgfältigen Programmdesigns. Das Verbergen eines Thread in einer Klasse oder in einer Bibliotheksfunktion allein hilft hierbei nicht weiter.
| Objektbedingte Probleme |
|
Es folgt aber dennoch eine Klasse für Threads. Wenn diese auch nicht die Widrigkeiten der Threadprogrammierung vollständig verbergen kann, so kann sie die Programmierung dennoch erleichtern. Außerdem steht diese Threadklasse für drei Betriebssysteme zur Verfügung:
- OS/2
- Windows 95
- Windows NT
| Klasse für Threads |
|
In der Headerdatei befindet sich die Definition der Klasse Thread. Die Klasse kann eine xThread Exception auslösen, wenn der Destruktor aufgerufen wird, obwohl sich der Thread noch nicht beendet hat. Die Klasse xThread ist in der Datei xthread.h definiert.
| Headerdatei |
|
Die Klasse Thread bietet Elementfunktionen für die üblichen Threadfunktionen. Ein paar Besonderheiten sind aber doch wegen der äußeren Umstände zu berücksichtigen. Die Adresse einer Memberfunktion kann nur ermittelt werden, wenn es sich um eine statische Memberfunktion handelt. Diese Adresse wird für die Funktion _beginthread benötigt. Diese statische Funktion kann für alle Objekte nur einmal vorhanden sein, was aber keine Probleme bereitet.
Die Funktion threadFunctionWrapper benötigt keine globalen Daten, sondern erhält den Zeiger auf den aktuellen Thread als Parameter. Der Kopierkonstruktor, der einen Zeiger auf ein Thread-Objekt als Parameter hat, und der Zuweisungsoperator sind protected deklariert. Für sie existiert keine Definition, somit können sie auch nicht aufgerufen werden. Diese drei Funktionen machen im Zusammenhang mit Threads keinen Sinn. Sollte sich aber dennoch eine Notwendigkeit ergeben, diese Funktionen zu implementieren, so kann dies in einer abgeleiteten Klasse durchgeführt werden.
| Klasse Thread |
|
Die Klasse Thread ist eine abstrakte Basisklasse, da die Funktion threadFunction in einer abgeleiteten Klasse definiert werden muß. Objekte der Klasse Thread können nicht instanziert werden.
| Abstrakte Basisklasse |
|
Die Klasse xThread ist in der Headerdatei definiert. Sie leitet sich von der Exception-Basisklasse xmsg ab. Neu in dieser Klasse ist das Element Id, das die Thread-ID des Betriebssystems enthält. Dieses Element ist public. Wenn eine Ausnahme ausgelöst wird, so ist mit dem Programm etwas nicht in Ordnung, und die Information der Exceptionklasse braucht in diesem Fall nicht vor globalen Zugriffen geschützt zu werden. Der Konstruktor initialisiert die Basisklasse xmsg mit dem als ersten Parameter übergebenen String und das Element Id mit dem zweiten Parameter. Die Headerdatei mit einer einfachen Klasse xmsg ist auf der Diskette vorhanden, falls der verwendete Compiler diese Klasse nicht unterstützt.
Implementierung für OS/2
Die Implementierung der Klasse Thread für OS/2 befindet sich in der Datei THREAD.CPP.
| Klasse xThread |
|
Der Konstruktor initialisiert die Elemente der Klasse. Der Stack wird auf 4096 Bytes gesetzt. Diese Größe kann mit der Elementfunktion setStackSize geändert werden. Der Status des Thread ist init. Er wird noch nicht gestartet. Am Ende (Destruktor) findet kein automatisches Warten auf den Thread statt.
| Konstruktor |
|
Der Destruktor prüft, ab der Thread bereits terminiert ist. Läuft er noch, so wird auf sein Ende gewartet, wenn das Flag fWaitOnExit gesetzt ist. Ist das Flag nicht gesetzt, so wird der Thread beendet und eine Ausnahme ausgelöst.
| Destruktor |
|
Die Größe des Threadstack kann mit der Funktion setStackSize geändert werden. Voreingestellt sind 4096 Bytes. Der Aufruf dieser Funktion ist aber nur vor dem Aufruf der Memberfunktion start wirksam.
| Funktion setStackSize |
|
start startet den Thread. threadFunktionWrapper wird als Threadfunktion verwendet. Diese Funktion ist statisch, so daß von ihr die Adresse an die _beginthread-Funktion übergeben werden kann. Konnte der Thread gestartet werden, so ist der Zustand des Objekts running. Verlief der Start erfolglos, so ist der Zustand error.
| Funktion start |
|
threadFunctionWrapper ruft die eigentliche Threadfunktion auf. Sie ist notwendig, da nur von einer statischen Elementfunktion die Adresse ermittelt werden kann. Diese Adresse benötigt die Funktion _beginthread. Außerdem kann die Funktion _endthread hier aufgerufen werden. So muß dies nicht die eigentliche Threadfunktion tun, was eine Fehlerquelle vermeidet. Die eigentliche Threadfunktion heißt threadFunction und wird über den Objektzeiger aufgerufen. Den Objektzeiger erhält die Funktion über den Parameter. Der Status des Objekts ist finished.
| Funktion threadFunctionWrapper |
|
suspend unterbricht den Thread. Der neue Zustand ist suspended.
| Funktion suspend |
|
resume startet den Thread erneut. Der neue Zustand ist running.
| Funktion resume |
|
block wartet auf das Ende der Threadfunktion.
| Funktion block |
|
kill beendet den Thread. Nach dem Ende befindet er sich im Zustand finished.
Implementierung für Win32
Die Deklaration der Klasse Thread für Win32 ist nahezu identisch zu der der Klasse Thread für OS/2. Im privaten Bereich ist zusätzlich ein Handle für die systemspezifische Kennung des Thread. Win32 identifiziert alle Ressourcen über Handles. Dieser unterschiedliche private Teil spielt aber bei der Anwendung der Klasse Thread keine Rolle. Programme, die die Klasse Thread verwenden, können vollkommen identisch gestaltet werden.
| Funktion kill |
|
Der Konstruktor erzeugt ein neues Thread-Objekt, aber noch keinen Thread. Der Thread befindet sich im Zustand init. Beim Beenden des Objekts wird der Thread gewaltsam beendet.
| Konstruktor |
|
Der Destruktor löscht ein Thread-Objekt. Ist der Thread selbst noch nicht beendet und soll auf ihn gewartet werden, so erledigt dies die Funktion block. Soll nicht auf den noch aktiven Thread gewartet werden, wird er durch den Aufruf der Funktion kill beendet. In diesem Fall wird eine xThread-Ausnahme ausgelöst.
| Destruktor |
|
Mit setStackSize läßt sich die Stackgröße des Thread verändern. Diese Funktion muß vor der Memberfunktion start aufgerufen werden.
| Funktion setStackSize |
|
start startet den Thread. Die _beginthread-Funktion ist in den Bibliotheken der Compiler von Borland und Watcom unterschiedlich implementiert.
| Funktion start |
|
threadFunctionWrapper ist eine Hülle um die eigentliche Threadfunktion. Eine kleine Schwierigkeit existiert bei der Win32-Implementierung. Das Handle des Threads erhält man durch den Aufruf der Funktionen GetCurrentProcess und DuplicateHandle. Dieses Handle benötigt man, um später auf den Thread zugreifen zu können. Diese Vorgehensweise ist ungewöhnlich und in den Dokumentationen nicht offensichtlich. Die eigentliche Threadfunktion threadFunction wird aufgerufen und somit die Bearbeitung des Thread gestartet. Nachdem sich der Thread beendet hat, wird das duplizierte Handle geschlossen und der Thread mit der Funktion _endthread ordnungsgemäß beendet.
| Funktion threadFunctionWrapper |
|
Mit suspend läßt sich ein laufender Thread anhalten. Dieser Zustand hält so lange vor, bis der Thread durch den Aufruf der Memberfunktion resume wieder aktiviert wird.
| Funktion suspend |
|
resume startet einen mit suspend angehaltenen Thread erneut.
| Funktion resume |
|
Mit block kann auf das Ende eines Thread gewartet werden. Dieses Warten erledigt die Funktion WaitForSingleObject. Die Entscheidung, auf welches Objekt gewartet werden soll, übernimmt Win32 anhand des Handles, das als erster Parameter übergeben wird.
| Funktion block |
|
Mit kill kann ein Thread bedingungslos beendet werden.
Demonstrationsprogramm für OS/2
In der Datei THRDEMO.CPP befindet sich ein Programm für OS/2, das die Klasse Thread verdeutlicht.
| Funktion kill |
|
Von der abstrakten Basisklasse Thread können nicht direkt Objekte angelegt werden. Dies wäre nicht sinnvoll, da keine Threadfunktion definiert ist. Die Klasse DemoThread ist von der Klasse Thread abgeleitet. Sie hat das zusätzliche Element Id und definiert die Funktion threadFunction ebenso wie einen neuen Konstruktor.
| Klasse DemoThread |
|
Der Konstruktor der Klasse DemoThread initialisiert die Id
| Konstruktor |
|
threadFunction ist die Funktion, die der Thread ausführt. Sie gibt zehnmal im Abstand einer Sekunde eine Meldung aus. Das Warten auf die nächste Ausgabe erledigt die Funktion DosSleep. Der Parameter der Funktion DosSleep sind Millisekunden. Die Ausgabe ist ein kritischer Bereich. Kritische Bereiche sollten möglichst kurz sein. Innerhalb eines kritischen Bereichs findet kein Taskwechsel zu einem anderen Thread derselben Applikation statt.
| Funktion threadFunction |
|
main legt 5 Demo-Thread-Objekte an. Dieses Anlegen gestaltet sich etwas umständlicher als gewohnt. Die Ursache ist der nicht definierte Kopierkonstruktor. Würden die Threads als Array angelegt werden, so wäre der Kopierkonstruktor notwendig. So behilft man sich für eine komfortablere Programmierung mit einem Array von Zeigern auf die angelegten Objekte. Der Aufruf der Funktion WaitForThread für pThread[4] ist auskommentiert wie der Aufruf block für diesen Thread. Somit wird am Programmende eine Ausnahme von diesem Thread ausgelöst, da der Destruktor am Ende der main-Funktion ausgeführt wird, während der Thread noch aktiv ist. Hebt man einen der beiden Kommentare auf, so wartet die main-Funktion, bis auch dieser Thread sich beendet. Nach dem Start aller Threads wartet das Programm zuerst 3 Sekunden. Der Thread pThread[4] wird unterbrochen, der Thread pThread[1] beendet. Das Programm (der Hauptthread) wartet auf die ersten vier Threads. Jetzt darf der Thread pThread[4] weiterlaufen. Wird noch auf das Ende dieses Thread gewartet, indem einer der beiden Kommentare entfernt wird, so beendet sich das Programm normal. So wie das Programm oben vorliegt, endet es mit einer Ausnahme, die im catch-Block behandelt wird.
Demonstrationsprogramm für Win32
Ein Beispiel für die Anwendung der Thread-Klasse unter Win32 ist in der Datei THRDEMO.CPP enthalten.
Innerhalb eines Prozesses können in Win32 Threads durch einen kritischen Bereich serialisiert werden. Nur ein Thread des Prozesses kann sich jeweils innerhalb des kritischen Bereichs befinden. Die benötigte Datenstruktur ist vom Typ CRITICAL_SECTION.
| Funktion main |
|
Die Klasse DemoThread ist von der nicht instanzierbaren Basisklasse Thread abgeleitet. Das private Datenelement der Klasse ist eine applikationsspezifische Kennung. Die Funktion threadFunction muß in allen abgeleiteten Klassen der Klasse Thread implementiert werden. Diese Funktion ist die eigentliche Threadfunktion. Der Konstruktor ist auch neu in der Klasse DemoThread. Ihm muß die Kennung des Thread als Parameter übergeben werden.
| Klasse DemoThread |
|
Der Konstruktor der Klasse DemoThread initialisiert die private Variable, die die Kennung des Thread enthält, mit dem Funktionsparameter.
| Konstruktor |
|
threadFunktion ist die eigentliche Threadfunktion. Sie gibt in einer Schleife zehnmal den Schleifenzähler und die applikationsspezifische Kennung des Thread aus. Damit sich die Ausgaben der einzelnen Threads nicht überschneiden, ist diese in einem kritischen Bereich gekapselt. Der kritische Bereich wird gebildet durch die beiden Funktionsaufrufe
- EnterCriticalSection
- LeaveCriticalSection
Nach jeder Ausgabe pausiert der Thread 1 Sekunde.
| Funktion threadFunktion |
|
Das Hauptprogramm main erzeugt fünf Threads und startet sie. Nach einer Pause von drei Sekunden wird der Thread mit der Kennung 4 angehalten und der Thread mit der Kennung 1 beendet. Der Hauptthread wartet, bis alle Threads sich beenden. Der Thread mit der Kennung 4 läuft nach dem Ende der anderen Threads wieder los. Wenn auch er sich beendet, beendet sich der gesamte Prozeß.
Diese Klasse und das zugehörige Programm sind nicht sehr kompliziert. Was aber das wesentliche ist, die Klasse Thread existiert für OS/2 und Win32. Die Klassendeklarationen sind zwar nicht ganz identisch. Sie unterscheiden sich aber nur in der private-Sektion, so daß die auf dieser Klasse basierende Anwendung vollkommen identisch ist.
| main
|