Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

28 Threads

28 Threads

Was ist ein Thread? Um dies zu beantworten, muss man wissen, wie Prozesse, aufgebaut sind. Jeder Prozess hat einen internen Hauptthread und einen vom Betriebssystem zugewiesenen Hauptspeicherbereich. Der Hauptthread wird im Hintergrund durch das Betriebssystem erzeugt. Im Hauptthread selbst wird nun der programmierte Code sequenziell, also hintereinander, ausgeführt. Es kommt somit also nie vor, dass zwei Befehle gleichzeitig ausgeführt werden. Damit man also mehrere Sachen gleichzeitig machen kann, muss man neben dem Hauptthread noch zusätzliche Workerthreads erzeugen, welche im Adressraum des Prozesses laufen. Somit sind zwar die einzelnen Threads grundsätzlich voneinander getrennt und besitzen eigene Stacks, allerdings ist es durchaus möglich, auf den gleichen Heap zuzugreifen, was eine Kommunikation zwischen den Threads ermöglicht.

Es gibt zahlreiche Situationen, in welchen es notwendig ist, dass, während ein Befehl ausgeführt wird, noch ein anderer Befehlt ausgeführt wird. Das fängt damit an, dass ein Programm etwas tut und man während dessen dem Anwender die Möglichkeit geben will, diese Arbeit abzubrechen und geht bis dahin, dass in einem Computerspiel mehrere Computergegner gleichzeitig agieren sollen. Generell in der grafischen Programmierung sind mehrere Threads nicht wegzudenken. Beispielsweise soll sich eine Statusanzeige bzw. ein Fortschrittsbalken während eines Vorganges ständig aktualisieren.

Da die Threadverwaltung und die damit verbundene Sheduling-Strategie, durch das Betriebssystem verwaltet wird, benutzt man zum Erzeugen von Threads Betriebssystemfunktionen. Ich werde mich hier lediglich um Windows kümmern und somit nur kurz auf die Win API eingehen. Nebenbei sei noch erwähnt, dass es seit Windows 7 möglich ist, s.g. User Mode Sheduled Threads anzulegen, für welche es möglich ist, die Kontrolle über das Sheduling in eigene Hände zu bekommen. Darauf werde ich allerdings nicht eingehen, da dies viel zu weit führen würde.

Was benötigt man also für einen Thread? Zunächst einmal braucht man eine Funktion, in welche man den auszuführenden Code packt und welche dann durch den Thread ausgeführt werden soll. Allerdings muss diese Funktion einer festen Konvention folgen, was zur Folge hat, dass man dieser Funktion nicht einfach Werte übergeben kann. Dafür kann man aber optional eine Datenstruktur anlegen, in welche man diverse Variablen mit Werten packt, welche der Thread bzw. die Threadfunktion benötigt. Sollen mehrere Threads etwas mit einer Datei machen, so kann man beispielsweise das Dateihandle oder nur den Dateipfad in eine solche Struktur packen. Der Threadfunktion wird dann ein Pointer auf diese Struktur übergeben. Der Rückgabewert eines Threads darf auch nicht beliebig sein, da man ihn üblicherweise für Fehlercodes benutzt. Gibt ein Thread null zurück, ist der Thread ordnungsgemäß zum Ende gekommen.

Beim Erzeugen des Threads stehen einem mehrere API Funktionen zur Verfügung, welche einem ein unterschiedliches Spektrum an Funktionalitäten bietet. Beispielsweise ist es u.a. möglich, die Priorität eines Threads anzugeben und ob er sofort gestartet werden soll oder ob er erst einmal pausiert gestartet wird und später mit arbeiten anfangen soll.

In den folgenden Teilkapiteln werde ich Ihnen zeigen, wie man zum einen Threads mit Hilfe der Win API erzeugt und zum anderen, wie man meine Wrapper (einmal als Template und einmal als Klasse) benutzt, welche ein wenig Arbeit abnehmen und somit den Umgang erleichtern. Hierfür werde ich jeweils zwei Threads erzeugen, welche auf der Konsole Zahlen ab einen Startwert aufsteigend ausgeben.

Zum Seitenanfang
Zum Inhaltsverzeichnis

28.1 Threads über die Win API

28.1 Threads über die Win API

Zu Beginn möchte ich zeigen, wie man mit den Win API Funktionen arbeitet. Dafür habe ich mich weitestgehend an dem Beispiel aus dem MSDN gehalten, nur dass ich es ein wenig vereinfacht habe (ich habe ein paar Sicherheitsmechanismen außer Kraft gesetzt). Schauen wir uns also die komplette "main" an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
					
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>

// Struktur, welche der Thread benutzt
typedef struct SMyThreadData {
	int iStart;
	int iEnd;
	int iID;
} SMyThreadData, *PMyThreadData;



// Threadfunktion /////////////////////////////////////////////////////////////
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
	PMyThreadData pData;
 
	pData = static_cast<PMyThreadData>(lpParam);

	for (int iCount = pData->iStart; iCount < pData->iEnd; iCount++) {
		printf("Thread: %i - %i\n", pData->iID, iCount);
		Sleep(10);
	} // end of while

	return 0; 
} // MyThreadFunction /////////////////////////////////////////////////////////



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	PMyThreadData	pData1		= (PMyThreadData)HeapAlloc(	GetProcessHeap(),
									HEAP_ZERO_MEMORY,
									sizeof(SMyThreadData)	);
	PMyThreadData	pData2		= (PMyThreadData)HeapAlloc(	GetProcessHeap(),
									HEAP_ZERO_MEMORY,
									sizeof(SMyThreadData)	);

	pData1->iStart			= 0;
	pData1->iEnd			= 10;
	pData1->iID			= 1;

	pData2->iStart			= 1000;
	pData2->iEnd			= 1010;
	pData2->iID			= 2;

	DWORD		dwThreadId1;
	DWORD		dwThreadId2;

	printf("START\n");

	HANDLE		hThreadArray1	= CreateThread(NULL, 0, MyThreadFunction, pData1, 0, &dwThreadId1);
	HANDLE		hThreadArray2	= CreateThread(NULL, 0, MyThreadFunction, pData2, 0, &dwThreadId2);

	WaitForMultipleObjects(1, &hThreadArray1, TRUE, INFINITE);
	WaitForMultipleObjects(1, &hThreadArray2, TRUE, INFINITE);

	CloseHandle(hThreadArray1);
	if (pData1 != NULL) HeapFree(GetProcessHeap(), 0, pData1);

	CloseHandle(hThreadArray2);
	if (pData2 != NULL) HeapFree(GetProcessHeap(), 0, pData2);

	printf("ENDE\n");

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

START
Thread 1 - 0
Thread 1 - 1
Thread 2 - 1000
Thread 1 - 2
Thread 2 - 1001
Thread 1 - 3
Thread 2 - 1002
Thread 1 - 4
Thread 2 - 1003
Thread 1 - 5
Thread 2 - 1004
Thread 1 - 6
Thread 2 - 1005
Thread 1 - 7
Thread 2 - 1006
Thread 1 - 8
Thread 2 - 1007
Thread 1 - 9
Thread 2 - 1008
Thread 2 - 1009
ENDE
		

Wie man gleich in Zeile 1 sieht, benötigen wir ein spezielles Include, welches uns eine Reihe von Funktionsprototypen bereit stellt, mit welchen wir die Threads realisieren können. Dann, in Zeile 6 bis 10, deklariere ich eine Datenstruktur, welche ich später als Container benutzen werde, um den Threads Informationen zuzuschieben. Bei dieser Typdefinition deklariere ich auch gleich noch einen Pointer auf jene Struktur.

In den Zeilen 15 bis 26 finden Sie dann die Funktion, welche durch den Thread ausgeführt werden soll. Gleich im Header der Funktion fallen ein paar Dinge sofort ins Auge. Da wäre zunächst der Rückgabetyp. "DWORD" stand für double Word, was wiederum einem Integer gleicht. Des weiteren steht dort ein Bezeichner "WINAPI". Dies hat was damit zu tun, dass diese Funktion eigentlich in einer DLL des Betriebssystems liegt und importiert werden muss (eigentlich bedeutet dies noch viel mehr, aber darum soll es uns jetzt nicht gehen). Anschließend folgt ein beliebiger Funktionsname. Als Parameter gibt man jetzt ein s.g. "LPVOID" Zeiger an. Dies bedeutet "large pointer of void", was wiederum einen üblichen "void" Zeiger entspricht. An jenem Zeiger wird dann die Datenstruktur gehangen. An dieser Schreibweise darf, abgesehen vom Funktionsnamen, nichts verändert werden. Am besten Sie kopieren das Gerüst eins zu eins. In Zeile 18 wandeln wir jetzt unseren Übergabeparameter in einen Zeiger auf die Datenstruktur um und könn anschließend die Schleife für die Ausgabe bauen.

Im restlichen Abschnitt folgt dann die "main" Funktion, welche den Hauptthread des Programms darstellt. Was hier passiert ist nicht ganz so leicht nachvollziehbar. Zuerst wird in den Zeilen 32 bis 37, zwei Datenstrukturen des definierten Typs im Prozessheap angelegt. Das dies so ist, werden Sie später noch sehen.

In Zeile 39 bis 45 setze ich jetzt einfach ein paar beliebige Werte, mit denen die zwei Threads dann arbeiten sollen. Anschließend, in Zeile 47 und 48, erzeuge ich mir zwei "DWORD" Variablen, in welchen später die Thread ID gespeichert wird. Jene werden ich in den Beispielen nie benutzen, aber sie könnte interessant sein, wenn man mit einem Prozessexplorer auf die Laufenden Prozesse mit ihren Threads schaut. Dort wird man dann den entsprechenden Thread unter genau dieser ID wiederfinden können.

In Zeile 52 und 53 werden jetzt die zwei Threads erzeugt und auch gleich gestartet. An dieser Stelle ist es wie bereits erwähnt möglich, den Thread auch pausiert zu starten. Dafür muss man bestimmte Flags setzen, welche Sie in der Hilfe nachlesen können.

Nun folgt etwas hochgradig gefährliches. In den Zeilen 55 und 56 rufe ich zwei mal eine Funktion auf, welche darauf wartet, dass ein bestimmter Thread zum ende kommt. Normalerweise kann man der Funktion ein Timeout übergeben, welches verhindert, dass diese Funktion so lange blockiert, bis der Thread zu Ende ist. Ich übergebe zwar auch ein Timeout, allerdings in Form einer Konstanten, welche ein extrem riesiges Timeout (entspricht also Unendlich) darstellt. Ich will dies an dieser Stelle wirklich erreichen, damit ich die Threads auch wirklich erst dann freigebe, wenn sie beendet wurden. Im Normalfall sollte man so etwas vermeiden. Würde man einen solchen Code in eine Fensterklasse einbauen, hätte dies zur Folge, dass das Fenster einfriert. Man könnte es noch nicht einmal mehr schließen. Besser ist es, lieber eine Schleife zu bauen, die immer wieder diese Funktion aufruft, bis der Thread tatsächlich zum Ende gekommen ist. So hätte man in der Zwischenzeit die Möglichkeit, andere Dinge, wie z.B. das Aktualisieren einer Statusanzeige, zu realisieren. Aber unterm Strich könnte es hier trotzdem passieren, dass das Programm in dieser Schleife auf ewig hängen bleibt (z.B. wenn der Thread abgestürzt ist oder auf etwas wartet, was nicht eintritt). In diesem Fall gäbe es die Möglichkeit den Thread mit Hilfe der Funktion "TerminateThread" abzuschießen. Dies wiederum hat aber zur Folge, dass evtl. gewisse Sachen nicht beendet werden. Sperrt die Threadfunktion z.B. etwas und wird während ihrer Arbeit terminiert, kann sie logischerweise entsprechende Sachen nicht freigeben. Somit ist es also klüger, in die Struktur eine Art Flag einzubauen, welche signalisiert, dass der Thread zum Ende kommen soll. Die Threadfunktion kann dann regelmäßig auf dieses Flag prüfen und ggf. vorzeitig seine Arbeit zum Ende bringen und somit ggf. notwendige Entsperrungen vornehmen.

Ab Zeile 58 kann ich jetzt davon ausgehen, dass die Threads definitiv zum Ende gekommen sind und nichts mehr tun und ich kann sie beenden bzw. freigeben. Dazu informiere ich das Betriebssystem darüber, dass ich das entsprechende Handle nicht mehr benötige und ähnlich der Dateiarbeit, sorgt das Betriebssystem für den Rest. Natürlich muss ich noch den erzeugten Heap wieder freigeben. Da ich ihn mit "HeapAlloc" erzeugt/angefordert habe, muss ich ihn mir "HeapFree" wieder freigeben.

An der Ausgabe sieht man, dass die Threads tatsächlich gleichzeitig gearbeitet haben. Allerdings wird die Ausgabe nicht immer so aussehen, da das Betriebssystem entscheidet, wann welcher Thread wie lange arbeitet. Es werden also immer die gleichen Zeilen auftauchen, nur ihre Reihenfolge kann variieren.

Zum Seitenanfang
Zum Inhaltsverzeichnis

28.2 Threads mit Hilfe einer Wrapperklasse

28.2 Threads mit Hilfe einer Wrapperklasse

Wie Sie eben gesehen haben, muss man ziemlich viel machen, um einen Thread zu starten und ihn zu verwalten. Deswegen scheint es klüger, diese ganzen Mechanismen in eine Klasse zu packen, welche Sie im Anhang finden können. Nun gibt es aber zwei Vorgehensweisen. Die erste, und in anderen Sprachen die gängige Variante, ist eine Basisklasse zu bauen und jene anschließend abzuleiten um die eigene Threadfunktion als Methode einzubauen. Ein Vorteil der sich daraus ergibt ist, dass man auf die zusätzliche Datenstruktur verzichten kann, da man alle notwendigen Variablen als Attribute der Klasse deklarieren kann. Entsprechend muss man dann entweder Setter oder einen geeigneten Konstruktor bauen. Wie so eine abgeleitete Klasse aussehen könnte, sehen Sie im folgenden Quelltext.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
					
#include "Thread.h"

// Wrapperklasse für einen Thread
class CMyThread : public CThread {
	private:
		int m_iSart;
		int m_iEnd;
		int m_iID;

	protected:
		// Threadfunktion /////////////////////////////////////////////////////
		inline virtual DWORD Execute(void) {
			for (int iCount = this->m_iSart; iCount < this->m_iEnd && !this->IsTerminated; iCount++) {
				printf("Thread: %i - %i\n", this->m_iID, iCount);
				Sleep(10);
			} // end of for

			return 0;
		}

	public:
		// Benutzerdefinierter Konstruktor ////////////////////////////////////
		inline CMyThread(int iStart, int iEnd, int iID)
				: CThread() // Konstruktor der Basisklasse aufrufen
				, m_iSart(iStart)
				, m_iEnd(iEnd)
				, m_iID(iID)
		{} // CMyThread ///////////////////////////////////////////////////////
};
					

In den Zeilen 6 bis 8 habe ich jetzt alle Variablen aus der vorherigen Struktur als Attribute meiner Klasse definiert. In den Zeilen 12 bis 19 überschreibe ich jetzt die abstrakte Methode der Basisklasse. Jene wird dann durch den Thread ausgeführt. Genau genommen wird eine statische Methode der Basisklasse ausgeführt, welche diese nur aufruft, was auch die vereinfachte Funktionsschnittstelle erklärt. Jene darf aber auch wieder nicht modifiziert werden. Der Inhalt dieser Methode wurde auch eingedampft und enthält jetzt nur noch die reine Logik der Funktion, da wir keine Struktur mehr umständlich holen und umwandeln müssen. Des weiteren habe ich in Zeile 13 die Bedingung der Schleife dahingehend erweitert, dass sie Vorzeitig beendet werden kann. Somit ist es einfacher möglich, den Thread zu Terminieren.

In den Zeilen 23 bis 28 habe ich jetzt einen eigenen Konstruktor implementiert, welchem ich alle notwendigen Daten übergeben kann, welche ich später brauche. Gleich in Zeile 24 sieht man, dass mein Konstruktor explizit den Konstruktor der Basisklasse aufruft, allerdings ohne Parameter, da es für jene Defaultwerte gibt, welche ich auch so verwenden will. Fall Sie also meine Klasse benutzen und den Konstruktor überschreiben, müssen Sie auf jeden Fall den Konstruktor der Basisklasse aufrufen, da Ihre Klasse ansonsten überhaupt nichts macht. Dies liegt daran, das ich die ganze Thematik des Threaderzeugens und dessen Initialisierung, in den Konstruktor gepackt habe.

Als nächstes werde ich Ihnen zeigen, wie jetzt die "main" aussieht und Sie werden sofort feststellen, dass man auch hier bedeutend weniger machen und vor allem beachten muss.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
					
#include <stdio.h>
#include "MyThread.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	CMyThread*	pThread1	= new CMyThread(0, 10, 1);
	CMyThread*	pThread2	= new CMyThread(1000, 1010, 2);

	printf("START\n");

	pThread1->Resume();
	pThread2->Resume();

	// Warten, bis alle Threads fertig sind
	while (!pThread1->IsFinished() || !pThread2->IsFinished()) Sleep(1);

	delete pThread1;
	delete pThread2;
	
	printf("ENDE\n");

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

In Zeile 8 und 9 werden jetzt zwei Threadobjekte erzeugt, an welchen der tatsächliche Thread hängt, wobei jene pausiert erzeugt werden. Dies liegt daran, dass in meiner Threadklasse dieses Verhalten als Standard vorgegeben ist und ich im Konstruktor der abgeleiteten Klasse kein abweichendes Verhalten angegeben habe. Dadurch muss ich in Zeile 13 und 14 die Threads von Hand starten. Dies kann wie gesagt vermieden werden. Diese Herangehensweise macht aber, wenn auch nicht in diesem Beispiel, durchaus Sinn, denn wenn man keinen Konstruktor hat, welcher alle notwendigen Werte befüllt, sondern auf Set Methoden zurückgreift, wäre es suboptimal, wenn der Thread loslegt, bevor er alle Werte hat und selbst wenn der Konstruktor alles initialisiert, so muss zu mindestens der Aufruf des Basisklassenkonstruktors als letztes in der Initialisierungsliste stehen, da es sonst auch hier dazu kommt, dass der Thread schon loslegt, bevor er alle Werte hat. Neben der Startfunktion "Resume", gibt es auch eine Methode "Suspend", welche den Thread vorübergehend pausieren kann. Zu einem Späteren Zeitpunkt kann dann wieder "Resume" aufgerufen werden, um den Thread fortzusetzen. Beide Methode basieren auch auf den Win API Funktionen, nur dass ich noch zusätzliche Abfragen eingebaut habe, die verhindern, dass man z.B. einen pausierten Thread nochmal pausieren kann und so weiter.

Anschließend gebe ich jetzt den Threads Zeit ihre Arbeit zu verrichten, denn wenn ich sie gleich wieder löschen würde, hätte dies zur Folge, dass sie nichts oder nur wenig machen. Dies liegt daran, dass ich im Destruktor der Threadobjekte ein Terminieren veranlasse, falls dieser noch nicht zum Ende gekommen ist. Dies ist notwendig, weil ich ein Threadhandle erst dann freigeben kann, wenn die Threadfunktion zum Ende gekommen ist. Löscht man also gleich die Threadobjekte, kann wieder der Effekt auftreten, dass die Threadfunktion mittendrin beendet wird und eventuelle Entsperrungen nicht mehr vornehmen kann. Da man aber allgemein nicht weiß, wie lange ein Thread braucht (zumal dies auf verschiedenen Rechnern variieren kann), ist es sehr Unklug, einfach eine Zeit X zu warten. Deshalb bietet meine Klasse die Möglichkeit zu prüfen, ob der Thread fertig ist. Dies macht man, wie Sie sehen, mit der Methode "IsFinished". Alternativ bietet meine Threadklasse jetzt aber die Möglichkeit, ihn sanft zu Terminiren, also ihm nur Bescheid zu geben, dass er bei nächster Gelegenheit zum Ende kommen soll. Dies macht man mit dem Aufruf der Methode "SoftTerminate".

Die Ausgabe dieser Demo ist wieder eine ähnliche, jedoch bin ich einigen schwer zu verstehenden Quelltext losgeworden bzw. habe ihn gut verlagert und somit ist die eigentliche Funktionalität losgelöst von der Verwaltung und somit leichter nachzuvollziehen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

28.3 Threads mit Hilfe einer Templateklasse

28.3 Threads mit Hilfe einer Templateklasse

In dieser Variante habe ich einen anderen Ansatz gewählt. Ich habe aus der zu Grunde liegenden eigenen Threadklasse ein Template gebaut. Dies hat zur Folge, dass ich keine eigene abgeleitete Klasse bauen muss, hab aber somit wieder den Nachteil, dass ich die auszuführende Funktion des Threads, mit in die "main" packen muss und auch die Syntax ist nicht mehr ganz so elegant. Jedoch ist dieses Beispiel dahingehend flexibler, wenn man ähnliche Threads bauen will, welche sich beispielsweise nur durch ihre Funktion und nicht ihren Variablen unterscheiden. Zudem benötige ich keine Vererbung und somit keine virtuellen Methoden mehr.

Das eingebundene Template finden Sie wieder im Anhand. Schauen wir uns nun an, wie man es benutzt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
					
#include <stdio.h>
#include "ThreadWrapper.h"

// Struktur, welche durch den Thread verwendet wird
struct SMyThreadData {
	int iStart;
	int iEnd;
	int iID;
};



// Durch den Thread auszuführende Funktion ////////////////////////////////////
DWORD MySimpleThreadFunc(SMyThreadData& sData) {
	for (int iCount = sData.iStart; iCount < sData.iEnd; iCount++) {
		printf("Thread: %i - %i\n", sData.iID, iCount);
		Sleep(10);
	} // end of for

	return 0;
} // MySimpleThreadFunc ///////////////////////////////////////////////////////



// Durch den Thread auszuführende Funktion ////////////////////////////////////
DWORD MyExtendedThreadFunc(CThreadWrapper<SMyThreadData>* pThread) {
	for (int iCount = pThread->m_tData.iStart; iCount < pThread->m_tData.iEnd && !pThread->IsTerminated(); iCount++) {
		printf("Thread: %i - %i\n", pThread->m_tData.iID, iCount);
		Sleep(10);
	} // end of for

	return 0;
} // MyExtendedThreadFunc /////////////////////////////////////////////////////



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	SMyThreadData			sData1		= {0, 10, 1};
	SMyThreadData			sData2		= {1000, 1010, 2};

	CThreadWrapper<SMyThreadData>*	pThread1	= new CThreadWrapper<SMyThreadData>(&MySimpleThreadFunc, sData1);
	CThreadWrapper<SMyThreadData>*	pThread2	= new CThreadWrapper<SMyThreadData>(&MyExtendedThreadFunc, sData2);

	printf("START\n");

	pThread1->Resume();
	pThread2->Resume();

	// Warten, bis alle Threads fertig sind
	while (!pThread1->IsFinished() || !pThread2->IsFinished()) Sleep(1);

	delete pThread1;
	delete pThread2;
	
	printf("ENDE\n");

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Zuerst, in Zeile 5 bis 9, brauche ich wieder eine Struktur, in welche ich die Daten packe, welche der Threadfunktion zur Verfügung gestellt werden sollen. Allerdings benötige ich jetzt keinen extra Pointertyp.

Anschließend, in Zeile 14 bis 21, folgt die erste Art der Threadfunktion. Sie ähnelt jetzt wieder der Schnittstelle, wie sie in Variante eins definiert wurde, nur dass das Schlüsselwort "WINAPI" nicht enthalten ist und ich jetzt nicht mehr einen "void" Zeiger übergebe, sondern die selbst definierte Struktur an sich. Alternativ könnte man hier auch ein Objekt definieren anstelle der Struktur und könnte das Objekt übergeben. Zu beachten ist, dass man innerhalb der Threadfunktion keinen Zugriff auf das Threadobjekt hat.

In den Zeilen 26 bis 33 sehen Sie jetzt die zweite Art der Funktion, die vom zweiten Thread benutzt werden soll. Der Funktionskopf sieht jetzt ein wenig anders aus, weil ich auf diesen Weg den Zugriff auf das Threadobjekt ermöglichen will. Bei dem Parametername ist zu beachten, dass man den selbst definierten Strukturtyp als Templateparameter mit angibt. Auch hier wäre es denkbar, einen Zeiger auf ein Objekt bzw. ein Objekt selbst zu übergeben. In der Zeile 26 sehen Sie jetzt, dass ich über die Threadvariable an die entsprechende Struktur herankomme und zudem noch alle anderen Funktionalitäten des Objektes nutzen kann. Somit ist es möglich, ein Beendigungswunsch entgegen zunehmen, sprich, auf den Aufruf von "SoftTerminate" zu reagieren. Dies erkaufe ich mir allerdings durch eine etwas komplexere Syntax.

Im letzten Abschnitt, zwischen den Zeilen 39 bis 58, sehen Sie wieder die "main" Funktion, in welcher ich die Threadobjekte erzeuge, starte und anschließend wieder beende. Bis auf die Tatsache, dass ich zu Beginn erst die notwendigen Strukturen erzeuge und die Erzeugung der Threadobjekte jetzt ein wenig anders aussehen, hat sich nichts geändert.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012