Programmieren unter MS-Windows

Kapitel 19: Windows NT - Threads

Letzte Änderung: 17.3.97 von B. Tritsch

Überblick

Zurück zum Index "PC- und MS-Windows-Support"

Zurück zum Inhalt


Prozesse und Threads

Ein Prozeß wird unter Windows NT gestartet, wenn man eine Applikation startet. Der Prozeß besitzt den Speicher, die Ressourcen und die einzelnen Programmfäden (Threads), die mit der Instanz eines laufenden, ausführbaren Programms assoziiert sind. Wenn ein Prozeß gestartet wird, wird daher auch ein primärer Thread gestartet. So lange zumindest ein Thread lebt, der mit dem Prozeß gekoppelt ist, solange läuft auch das Prozeß selbst.

Ein Thread ist die kleinste ausführbare Einheit unter Windows NT, ist immer mit einem Prozeß assoziiert und lebt immer innerhalb eines bestimmten Prozesses. Obwohl viele Prozesse während ihrer gesamten Lebensdauer nur einen Thread besitzen, kann ein Prozeß auch sehr viele Threads haben. Die Threads werden entsprechend ihrer "Priorität" vom Scheduler aufgerufen, wobei es 31 unterschiedliche Prioritätsstufen gibt.

Ein Thread kann sich in einem von sechs Zuständen befinden:

Zu jeder Zeit kann es nur einen Thread pro Systemprozessor im "Running"-Zustand geben. Dieser Thread läuft, bis sein "Time Slot" - vorgegeben vom Scheduler - vorüber ist, ein höher priorisierter Thread in den "Waiting"-Zustand geht oder der laufende Thread auf einen Event wartet.

Um einen Prozeß zu starten wird üblicherweiße die entsprechende Funktionalität des Explorers, des Startmenüs oder der Kommandozeile verwendet. Es gibt jedoch auch andere Möglichkeiten unter Windows NT. Eine davon ist der Befehl CreateProcess, die einen Prozeß aus einer anderen NT-Applikation startet. Nach Aufruf dieser API-Funktion kann eine Struktur, die als Parameter beinhaltet ist, ausgewertet werden. Die Auswertung ermöglicht den Zugriff auf Informationen über den gerade gestarteten Prozeß.

Um einen Prozeß wieder zu beenden, können zwei API-Befehle verwendet werden: ExitProcess und TerminateProcess. Die erste Funktion wird typischerweise aus einem Thread aufgerufen, der zu dem Prozeß gehört, und beendet den Prozeß sauber. Die zweite Funktion sollte vermieden werden, da sie keinerlei "Aufräumarbeiten" leistet.

Ein Tread kann mit dem API-Befehle CreateThread erzeugt und normalerweise mit ExitThread beendet werden. Der Befehl TerminateThread sollte aus den gleichen Gründen wie oben nicht verwendet werden. Die Priorität eines Threads läßt sich mit SetThreadPriority verändern.

Solange man lokale Variablen in Threads verwendet ist das Leben einfach: Jeder Thread erhält seinen eigenen Speicherbereich mit den lokalen Variablen. Verwendet man globale Variablen, müssen sie speziell behandelt werden. Hierbei sit sogar eine Unterscheidung zwischen statischen und dynamischen globalen Variablen nötig.

Synchronisation von Threads

Sobald ein Prozeß in mehrere Threads zerlegt wird, müssen diese unter Umständen miteinander Kommunizieren und daher synchronisiert werden. Da die Synchronisation von Threads kein triviales Problem ist, sollte vor der Verwendung mehrerer Threads in einem Prozeß die Überlegung stehen, ob dieses überhaupt nötig ist. Ein Applikation ist wahrscheinlich ein guter Kandidat um in mehrere Threads aufgeteilt zu werden, wenn

Eine Applikation ist ein problematischer Kandidat für mehrer Threads, wenn

Anmerkung: Die Verwendung von mehreren Threads kann Probleme in der Anwendungsentwicklung verringern, wird jedoch auch neue Probleme hinzubringen!

Wenn nun zwei oder mehr Threads eine gemeinsame Variable benutzen, können Schwierigkeiten auftreten. Wollen die Threads die Variablen nur auslesen, so erzeugt dies noch keine Probleme. Erst wenn verschiedene Threads versuchen die gemeinsame Variable zu modifizieren, so muß die Variable synchronisiert werden. Ein Synchronisations-Primitiv ist hierbei ein Objekt, das dabei hilft, multithreaded Applikationen zu managen. Unter Windows NT existieren fünf Basistypen solcher Primitive:

Events

Events sind Objekte, die vom Entwickler erzeugt werden und anzeigen, daß eine Variable oder Routine frei für den Zugriff ist. Hierfür wird eine Nachricht zum entsprechenden Zeitpunkt an einen Empfänger zu schicken. Diese Art der Synchronisation wird verwendet, wenn ein oder mehrere Threads auf die Vollendung der Aufgabe eines anderen Threads warten müssen.

Ein entsprechender Event wird mit der Funktion CreateEvent erzeugt. Mit SetEvent bzw. PulseEvent wird der Zustand des Events von Not Signaled (der wartende Thread ist blockiert) zu Signaled (der wartende Thread wird freigegeben) oder umgekehrt geändert. Der wartende Thread wurde vorher mit WaitForSingleObject oder WaitForMultipleObjects in den zugehörigen Zustand gebracht.

Critical Sections

Critical Sections sind Bereiche im Code, die nur einem Thread zu einer bestimmten Zeit Zugriff gewähren. Versucht ein zweiter Thread auf das Code-Segment zuzugreifen, wird er geblockt. Die Verwaltung einer Critical Section erfordert nur wenig Computer-Ressourcen, jedoch kann dieses Synchronisations-Primitv nur innerhalb eines einzelnen Prozesses verwendet werden.

Die Critical Section wird durch die Variable CRITICAL_SECTION bestimmt. Diese muß über die Funktion InitializeCriticalSection initialisiert werden. Der Zugriff erfolgt dann über EnterCriticalSection und wird mit LeaveCriticalSection beendet.

Mutexes

Mutexes oder Mutual Exclusions sind NT-Objekte, die benutzt werden um zu versichern, daß nur ein einziger Thread Zugang zu einer geschützten Variable oder einem geschützten Code-Segment hat. Im Gegensatz zu Critical Sections sind Mutexes NT-Objekte und werden daher vom Betriebssystem verwaltet. Damit können sie auch zwischen Prozessen wirken.

Semaphore

Semaphore sind ähnlich wie Mutexes, verhalten sich jedoch eher wie Zähler. Sie erlauben einer vorher spezifizierten Anzahl von Threads auf eine Variable oder ein Code-Segment. Um einen Semaphor zu erzeugen, wird die Funktion CreateSemaphore verwendet, die Rückgabe der Semaphor-Ressource geschieht mit ReleaseSemaphore.

Atomare Operationen

Atomare Operationen werden von Windows NT zur Verfügung gestellt, um innerhalb einer einzigen Operation Variablen zu erhöhen, erniedrigen oder den Inhalt zu ändern.

Zum nächsten Kapitel