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...

30 CriticalSections

30 CriticalSections

Wie bereits angedeutet, ist es wichtig, ganze Quelltextabschnitte vor gemeinsamen Zugriff (und somit auch vor der Nutzung mehrerer Variablen) zu schützen. Möchte man von Hand eigene Mechanismen entwerfen, um solchen Situationen aus dem Weg zu gehen, wird man schnell merken, dass dies gar nicht so einfach ist, da man oft das Problem nur auf eine andere Stelle verschiebt oder das es zu unangenehmen Verklemmungen kommen kann. Deswegen bietet einem das Betriebssystem Mechanismen am, mit welchen wir eine Sperrung bzw. Schutz für s.g. kritische Abschnitte, vornehmen können. Das erste Werkzeug sind die kritischen Abschnitte.

Mit einer "CriticalSection" kann man quasi erreichen, dass immer nur einer einen bestimmten Quelltextabschnitt betritt und ausführen kann. Man muss sich das jetzt wie beim analogen Telefon vorstellen. Besitzt man in seiner Wohnung nur einen analogen Anschluss und eine Telefonanlage mit zwei oder mehr Apparate, dann kann immer nur einer die Leitung, also die Ressource, nutzen. Alle anderen bekommen ein Besetztzeichen und müssen warten.

Im vorletzten Kapitel sah man, dass zwar die Ausgaben der Threads zeilenweise gemischt waren, aber nicht innerhalb einer Zeile. Dies liegt daran, dass die "printf" Funktion intern einen solchen Mechanismus benutzt, damit die gesamte Zeile geschrieben werden kann, ohne das jemand anderes dazwischen funkt.

Zum Seitenanfang
Zum Inhaltsverzeichnis

30.2 CriticalSections mit Hilfe interner Bibliotheken

30.2 CriticalSections mit Hilfe interner Bibliotheken

Da der Umgang mit der Win API immer ein wenig mühselig ist, hat Microsoft innerhalb von Visual Studio ein paar Klassen bereitgestellt, welche den Zugriff auf die API kapseln. Somit wird der Aufwand ein wenig geringer und auf jeden Fall besser lesbar. Schauen wir uns also das gleiche Beispiel von eben an, nur mit der entsprechenden Klasse der MFC.

 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
					
#define _AFXDLL
#include "afxmt.h"

#include <stdio.h>
#include "ThreadWrapper.h"

struct SMyThreadData {
	int			iStart;
	int			iEnd;
	int			iID;
	CCriticalSection*	pCriticalSection;
};



// Funktion welche durch den Thread ausgeführt wird ///////////////////////////
DWORD ThreadFunc(CThreadWrapper<SMyThreadData>* pThread) {
	__try {
		pThread->GetData().pCriticalSection->Lock();

		for (int iCount = pThread->GetData().iStart; iCount < pThread->GetData().iEnd && !pThread->IsTerminated(); iCount++) {
			printf("Thread: %i - %i\n", pThread->GetData().iID, iCount);
			Sleep(100);
		} // end of for
	} __finally {
		pThread->GetData().pCriticalSection->Unlock();
	} // end of try

	return 0;
} // ThreadFunc ///////////////////////////////////////////////////////////////



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

	CThreadWrapper<SMyThreadData>*	pThread1		= new CThreadWrapper<SMyThreadData>(&ThreadFunc, sData1);
	CThreadWrapper<SMyThreadData>*	pThread2		= new CThreadWrapper<SMyThreadData>(&ThreadFunc, 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;

	delete pCriticalSection;
	
	printf("ENDE\n");

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

Wie Sie in Zeile 1 bis 5 sehen, benutze ich jetzt nicht mehr die "windows.h", sondern eine MFC Bibliothek (Microsoft Foundation Classes C++ Library). Immer wenn man diese Bibliothek einbindet, muss man bestimme Sachen vorher definieren und man darf auch die "windows.h" nicht einbinden, da enthaltene Funktionensdefinitionen indirekt durch die MFC bereitgestellt werden und es ansonsten zu gegenseitigen Includes kommt. In der "afxmt.h" befinden sich eine Reihe von Klassen, mit welchen man Synchronisationen und kritische Abschnitte realisieren kann. Die von mir verwendete Klasse "CCriticalSection" ist beispielsweise von der Klasse "CSyncObject" abgeleitet, welche die Basisklasse der meisten Synchronisationsobjekte ist, welche ich später noch erklären werde.

In den Zeilen 7 bis 12 wird wieder die Struktur für den Thread definiert und im Gegensatz zu vorhin, sieht der Zeiger in Zeile 11 etwas freundlicher aus.

In den Zeilen 17 bis 30 findet man jetzt wieder die Threadfunktion, welche bis auf zwei Kleinigkeiten wieder genauso aussieht. In Zeile 19 sehen Sie jetzt, dass das Betreten des kritischen Abschnittes, sich von einem Funktionsaufruf in ein Methodenaufruf gewandelt hat. Genau das Gleiche passiert auch in Zeile 26 beim verlassen des Abschnittes. Interessanterweise gibt es zum Betreten des Abschnittes wieder zwei Methoden, wobei man der Zweiten ein Timeout übergeben kann, welches bewirkt, dass der Thread maximal eine Zeit X versucht, diesen Abschnitt zu betreten. Dies wird intern wieder über die "TryEnterCriticalSection" Methode gelöst, welche in einer Schleife ausgeführt wird. Ein Timeout gibt es ja eigentlich nicht bei einem kritischen Abschnitt, aber wenn man schon einmal eine Klasse baut, kann man ja ein solches Verhalten mit implementieren, ohne dass man es von Außen mitbekommt.

In der "main" Funktion haben sich auch ein paar Kleinigkeiten geändert. Das Erzeugen des Abschnittswächters in Zeile 36 sieht jetzt, entsprechend dem objektorientierten Ansatz, anders aus. Entsprechend braucht man auch keine Funktion mehr aufrufen um den kritischen Abschnitt freizugeben, sondern löscht einfach das Objekt mit "delete", wie Sie das in Zeile 54 sehen können.

Zum Seitenanfang
Zum Inhaltsverzeichnis

30.1 CriticalSections mit Hilfe der Win API

30.1 CriticalSections mit Hilfe der Win API

Im folgenden Beispiel werde ich zeigen, wie man mit Hilfe der Win API einen kritischen Abschnitt realisieren kann. Dabei werde ich auf das Beispiel von vorhin aufsetzen, in welchem zwei Threads mehrere Sachen auf der Konsole ausgaben.

 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 <windows.h>
#include <stdio.h>
#include "ThreadWrapper.h"

struct SMyThreadData {
	int			iStart;
	int			iEnd;
	int			iID;
	LPCRITICAL_SECTION	pCriticalSection;
};



// Funktion, welche durch den Thread ausgeführt wird //////////////////////////
DWORD ThreadFunc(CThreadWrapper<SMyThreadData>* pThread) {
	__try {
		EnterCriticalSection(pThread->GetData().pCriticalSection);

		for (int iCount = pThread->GetData().iStart; iCount < pThread->GetData().iEnd && !pThread->IsTerminated(); iCount++) {
			printf("Thread: %i - %i\n", pThread->GetData().iID, iCount);
			Sleep(100);
		} // end of for
	} __finally {
		LeaveCriticalSection(pThread->GetData().pCriticalSection);
	} // end of __try

	return 0;
} // ThreadFunc ///////////////////////////////////////////////////////////////



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	CRITICAL_SECTION		sCriticalSection;
	InitializeCriticalSection(&sCriticalSection);

	SMyThreadData			sData1		= {0, 10, 1, &sCriticalSection};
	SMyThreadData			sData2		= {1000, 1010, 2, &sCriticalSection};

	CThreadWrapper<SMyThreadData>*	pThread1	= new CThreadWrapper<SMyThreadData>(&ThreadFunc, sData1);
	CThreadWrapper<SMyThreadData>*	pThread2	= new CThreadWrapper<SMyThreadData>(&ThreadFunc, 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;

	DeleteCriticalSection(&sCriticalSection);
	
	printf("ENDE\n");

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

Ausgabe:

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

In den Zeilen 5 bis 10 sieht man wieder die Definition der Datenstruktur, welche dem Threadobjekt übergeben wird. Hier ist jetzt noch ein Zeiger für den kritischen Abschnitt hinzu gekommen, da ich für beide Threads den gleichen verwenden will.

Wie man dann in den Zeilen 15 bis 28 sehen kann, benutze ich für die Threads meine Templateklasse und die zweite Art der Threadfunktion, welche mein Template erlaubt. Als aller erstes betrete ich einen "try-finaly" Block. Dies ist eine reine Vorsichtsmaßnahme, denn falls nach dem Betreten des Abschnittes ein Laufzeitfehler auftritt, habe ich sonst keine Möglichkeit mehr, diesen kritischen Abschnitt wieder zu verlassen. Anschließend, in Zeile 17, betrete ich nun diesen kritischen Abschnitt und kann dann meine eigentliche Arbeit verrichten. Wie man an der Schleifenbedingung sehen kann, baue ich wieder ein Mechanismus ein, welcher es mir erlaubt, die Arbeit des Threads vorzeitig zu beenden. In Zeile 24 verlasse ich wieder den Abschnitt und komme zum Ende der Threadfunktion.

Ab der Zeile 33 finden Sie jetzt die "main" Funktion. Dort ist jetzt lediglich das Erzeugen des kritischen Abschnittes, sowie seine Freigabe, hinzugekommen. Außerdem übergebe ich die Referent des Abschnittswächters an die verwendeten Strukturen. Das Anlegen eines kritischen Abschnittes, findet in Zeile 34 und 35 statt. Das Freigeben findet man in Zeile 54.

Wie Sie jetzt an der Ausgabe sehen können, haben beide Threads wieder die gemeinsame Konsole verwendet, aber darauf gewartet, bis der andere mit seiner Ausgabe fertig ist. Ich hatte zusätzlich in der Threadfunktion das "Sleep" hoch gesetzt, um dieses Verhalten mehr zu unterstreichen. Zwar ist dies in der Ausgabe nicht ersichtlich, aber wenn Sie sich diesen Quelltext kopieren und ausführen, werden Sie sehen, dass der erste Thread jetzt eine Weile braucht, um zum Ende zu kommen und das der zweite der Weile wartet, bevor er loslegt.

Nun kann man statt der Funktion "EnterCriticalSection" auch die Funktion "TryEnterCriticalSection" benutzen, welche sofort "false" zurück gibt, falls schon jemand in diesem Abschnitt ist. Der Thread der nicht rein kommt, kann jetzt entweder in einer Schleife darauf warten, dass er irgendwann doch noch rein kommt oder etwas ganz anderes tun und somit diesen Bereich überspringen. Dadurch verhindert man, dass ein Thread Ewigkeiten auf etwas wartet, was im schlimmsten Fall nie eintritt.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012