Multithreading mit der WinAPI

Inahlt:
- Allgemein
- Implementierung
- lpStartAddress – Unsere Thread Funktion
- Threads synchronisieren
- Die Threads kontrollieren
- Zusammenfassung
- Referenzen

Allgemein
Erst einmal stellt sich die Frage, warum Multithreading verwenden? Der Vorteil liegt ganz klar darin, dass man Mehrere Dinge gleichzeitig ausführen kann. Am Beispiel eines Spiels bedeutet dass zum Beispiel, dass man die Spielphyik und das Rendering gleichzeitig ausführen kann. (zu den Einschränkungen kommen wir später ;) ). Unnötig hier zu sagen, dass das einen guten Geschwindigkeitsvorteil mit sich bringt. Doch auch die schönste Methode bringt Nachteile mit sich. Diese hören hier auf Synchronisation, denn es wäre fatal, wenn mehrere Threads gleichzeitig auf ein und das selbe Byte zugreifen.

Implementierung
Ein Thread wird unter der WinAPI durch ein “HANDLE” Repräsentiert. Um einen Solchen zu erzeugen können wir die Funktion CreateThread der WinAPI verwenden:

HANDLE WINAPI CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);

Gehen wir die Parameter einmal durch:
lpThreadAttrinbutes:
dazu später
dwStackSize:
Die Größe des Stacks für diesen Thread. Wenn man hier 0 angibt, wird die Standardgröße verwendet.
lpStartAddress:
dazu später
lpParameter:
Ein Zeiger auf zusätzliche Parameter, die wir unserem Thread mit auf den Weg geben können. Wer keine Dinge an seinen Thread übergeben will, einfach NULL angeben.
dwCreationFlags:
Hier können wir unter anderem CREATE_SUSPEND oder gar nichts angeben. Wenn wir CREATE_SUSPEND angeben, startet der Thread nicht gleich, sondern wartet darauf, dass wir ihn manuell später Starten. Wie wir das machen, erläre ich später.
lpThreadId:
Die ID des Threads. Sie wird von der Funktion ausgefüllt.
Rückgabewert:
Diese Funktion gibt das Handle auf unseren Thread zurück.

lpStartAddress – Unsere Thread Funktion

Jezt wissen wir zwar, wie wir einen Threads erzeugen, doch nicht, was wir in unseren Code schreiben müssen.
Jeder Thread wird durch eine Funktion repräsentiert. (z.B. ist unsere main / WinMain-Funktion gewissermaßen auch die Einsprungsfunktion eines Threads). Sie ist im Prinzip der Ganze Thread, wenn sie endet, endet auch der ganze Thread. Sie muss das folgende Format haben:

DWORD WINAPI ThreadProc (LPVOID lpParameter);

Mit “lpParameter” begegnen wir hier dem Parameter unserer CreateThread Funktion wieder.
Jezt können wir endlich unsere Erste Multithreading Anwendung erstellen:

// Includes
#include <iostream>
#include <windows.h>

//
DWORD WINAPI Thread1Proc (LPVOID SpecArg)
{
 // Ausgabe
 std::cout << "Hallo, hier ist Thread1" << std::endl;

 // Ende
 return 0;
}

/// main
///
///
int main ()
{
 //
 HANDLE    Thread1;
 DWORD    Thread1ID;

 //
 Thread1 = CreateThread (NULL, NULL, &Thread1Proc, NULL, NULL, &Thread1ID);

 Sleep (1000);

 // Ende, noch kurz warten
 std::cin.get();
 return 0;
}

Die Ausgabe von Thread1 läuft Parallel zu dem “Schlafen” von unserem Hauptthread. Doch warum dieses Sleep (1000) im Haputthread. Ganz einfach: Ich will sichergehen, dass das cin.get () nach dem cout von Thread1 ausgeführt wird. Doch das sollte auch anderst, und “schicker” zu erledigen zu sein. Damit kommen wir zu unserem Nächsten Problem:

Threads synchronisieren
Wir sollten unser Problem in 2 Teilprbleme zerlegen:
1. Wie sage ich dem Einen Thread, dass ich etwas erledigt habe ?
Zu diesem Zweck gibt es “Events”. Ein Thread kann ein Event erzeugen, und ein anderer darauf warten. Der ganze Verwaltungsaufwand dabei wird uns von Windows (freundlicherweise) abgenommen, wir können das Event mit “CreateEvent” erzeugen und mit “WaitForSingleObject” / “WaitForMultipleObjects” auf ein Event warten. In der Praxis sieht das dann so aus:

 HANDLE FinishEvent;
<strong>in der main:</strong>
 // Das Event erzeugen
 FinishEvent = CreateEvent (NULL, false, /* soll das Event automatisch wieder resettet werden */
 false, /* der Start-Status bzw. Soll das Event gleich gesendet werden ? */ TEXT("Thread1Fertig") /* ein Name */);

 // Auf das Event warten
 WaitForSingleObject (FinishEvent, INFINITE);
in der Thread1Proc:
 // Das Event aussenden
 SetEvent (FinishEvent);

2. Wie verhindere ich, dass beide Threads gleichzeitig auf ein Objekt zugreifen
Zu diesem Zweck wurde “CRITICAL_SECTION” erfunden. Es kann immer nur 1 Thread gleichzeitig Eine Critical Section besitzen, nicht mehr. Alle anderen Threads, die eine CS wollen müssen solange warten, bis der Thread, der sie gerade hat, sie wieder freigiebt. Das Alles läuft wie Folgt ab:
- InitializeCriticalSection muss vor der ersten Verwendung der CS aufgerufen werden
- DeleteCriticalSection gibt sie wieder frei, muss am ende des Programms aufgerufen werden
- EnterCriticalSection der Thread der Das aufruft, meldet seinen Anspruch auf die CS an, bekommt sie entweder gleich oder muss solange warten.
- LeaveCriticalSection Ein Thread meldet mit dieser Funktion seinen Anspruch auf die CS ab, der nächste kann dran kommen.

Die Threads kontrollieren
Vorher war die Rede davon, einen Thread manuell zu kontrollieren. Wie das geht ?
SuspendThread – Pausiert einen Thread
ResumeThread – Nimmt einen Pausierten Thread wieder auf
TerminateThread – Zerstört den Thread
Diese 3 Funktionen nehmen jeweils als Parameter das Handle des Threads.

Zusammenfassung
In diesem Tutorial haben wir gelernt, wie wir Threads verwenden, und ein Paar Problemen bei der Synchronnisation aus dem Weg gehen.

Referenzen
http://msdn.microsoft.com/en-us/library/ms685086%28VS.85%29.aspx
http://msdn.microsoft.com/en-us/library/ms684847%28VS.85%29.aspx
http://msdn.microsoft.com/en-us/library/ms686345%28VS.85%29.aspx
http://msdn.microsoft.com/en-us/library/ms682453%28VS.85%29.aspx