SDL: Threads
Threads dienen dazu, Aufgaben gleichzeitig oder scheinbar gleichzeitig durchzuführen. Es kann unter Umständen sinnvoll sein, Threads einzurichten, die sich beispielsweise um die Musik während eines Spieles kümmern. Bitte beachte Sie, dass Threads selbst durch den Wechsel von einem Thread zu einem anderen Rechenzeit kosten. Unter Umständen wird eine Anwendung mit Threads langsamer laufen als ohne. Echt Gleichzeitig können Aufgaben nur dann erfolgen, wenn Sie Gebrauch von einer zweiten CPU machen können.
Threads einrichten und verwalten
[Bearbeiten]Threads erzeugen
[Bearbeiten]Ein Thread wird repräsentiert durch eine Funktion folgender Gestalt
int mein_thread (void* parameter);
Um einen Thread zu erzeugen, ruft man folgende Funktion auf:
SDL_Thread *SDL_CreateThread (int (*thread_func)(void*), void *thread_parameter);
Diese Funktion erwartet obige Funktion als Argument, der Parameter thread_parameter wird direkt an den eigentlichen Thread übergeben.
Threads beenden
[Bearbeiten]Die Funktion
void SDL_KillThread (SDL_Thread *thread);
beendet den laufenden Thread sofort. Der Thread hat keine Möglichkeiten mehr, evtl. anstehende Aufräumarbeiten zu erledigen.
Warten auf das Ende
[Bearbeiten]Mit der Funktion
void SDL_WaitThread (SDL_Thread *thread, int *status);
wartet man auf das Beenden des Threads. Man wartet unendlich lange, wenn der Thread eine Endlosschleife implementiert. Der Rückgabewert der Threadfunktion wird in status übergeben.
Sonstiges
[Bearbeiten]Die Funktionen
Uint32 SDL_GetThreadID(SDL_Thread *thread);
und
Uint32 SDL_ThreadID (void);
übergeben eine 32-Bit Zahl, die den Thread eindeutig benennt.
Beispiel
[Bearbeiten]Das folgende Beispiel implementiert zwei Threads. Innerhalb der Threads werden Rechtecke an zufälligen Positionen erzeugt
rect.x = 1 + (int) (630 * (rand() / (RAND_MAX + 1.0))); rect.y = 1 + (int) (470 * (rand() / (RAND_MAX + 1.0)));
und anschließend gezeichnet. Die Threads selbst werden mit
t1 = SDL_CreateThread (thread1, screen); t2 = SDL_CreateThread (thread2, screen);
erzeugt, der zusätzliche Parameter ist hier der Zeiger auf die Surface des Bildschirmes. Drückt der Benutzer die Escape-Taste, so werden beide Threads beendet mit
SDL_KillThread(t1); SDL_KillThread(t2);
#include <stdlib.h> #include <SDL.h> #include <SDL_thread.h> int thread1 (void *p) { SDL_Rect rect; SDL_Surface *s = (SDL_Surface*) p; rect.x = 100; rect.y = 100; rect.w = 10; rect.h = 10; while (1) { rect.x = 1 + (int) (630 * (rand() / (RAND_MAX + 1.0))); rect.y = 1 + (int) (470 * (rand() / (RAND_MAX + 1.0))); SDL_FillRect (s, &rect, SDL_MapRGB (s->format, 255, 0, 0)); SDL_UpdateRect (s, rect.x, rect.y, rect.w, rect.h); SDL_Delay (100); } return 0; } int thread2 (void *p) { SDL_Rect rect; SDL_Surface *s = (SDL_Surface*) p; rect.x = 100; rect.y = 100; rect.w = 10; rect.h = 10; while (1) { rect.x = 1 + (int) (630 * (rand() / (RAND_MAX + 1.0))); rect.y = 1 + (int) (470 * (rand() / (RAND_MAX + 1.0))); SDL_FillRect (s, &rect, SDL_MapRGB (s->format, 0, 255, 0)); SDL_UpdateRect (s, rect.x, rect.y, rect.w, rect.h); SDL_Delay (100); } return 0; } int main (void) { SDL_Surface *screen; SDL_Event e; SDL_Thread *t1, *t2; int quit = 0; if (SDL_Init (SDL_INIT_VIDEO) == -1) { printf ("Kann SDL nicht initialisieren: %s\n", SDL_GetError ()); exit (1); } atexit (SDL_Quit); screen = SDL_SetVideoMode (640, 480, 16, SDL_SWSURFACE); if (screen == NULL) { printf ("Video Modus kann nicht eingerichtet werden: %s\n", SDL_GetError()); exit (1); } t1 = SDL_CreateThread (thread1, screen); t2 = SDL_CreateThread (thread2, screen); while (SDL_WaitEvent (&e) && !quit) switch (e.type) { case SDL_KEYDOWN: if (e.key.keysym.sym == SDLK_ESCAPE) { SDL_KillThread(t1); SDL_KillThread(t2); quit = 1; } break; } return 0; }
Bitte nicht stören
[Bearbeiten]Einleitendes Beispiel
[Bearbeiten]Das folgende Programm versucht, nacheinander die Zeichenketten "aaaaa" und "bbbbb" auszugeben, eine Pause einzulegen und dann wieder von vorne zu beginnen. Bitte beachten Sie, daß hier keine Video-Ausgabe erfolgt. Das Programm funktioniert rein im Shell-Modus ohne eigenes Fenster.
#include <stdlib.h> #include <SDL.h> #include <SDL_thread.h> int thread1 (void *p) { while (1) { printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf("\n"); SDL_Delay (200); } return 0; } int thread2 (void *p) { while (1) { SDL_Delay (10); printf ("b"); SDL_Delay (10); printf ("b"); SDL_Delay (10); printf ("b"); SDL_Delay (10); printf ("b"); SDL_Delay (10); printf("\n"); SDL_Delay (200); } return 0; } int main (void) { SDL_Thread *t1, *t2; if (SDL_Init (0) == -1) { printf ("Kann SDL nicht initialisieren: %s\n", SDL_GetError ()); exit (1); } t1 = SDL_CreateThread (thread1, NULL); t2 = SDL_CreateThread (thread2, NULL); SDL_Delay (2000); SDL_KillThread (t1); SDL_KillThread (t2); SDL_Quit (); return 0; }
Führen wir das Programm aus, passiert etwas sonderbares:
ababababab ababababab ababababab ababababab ababababab ababababab ababababab
Von einem "aaaaa", "bbbbb", wie wir es uns wünschen, ist wenig zu sehen, beide Threads überlagern sich und machen ihre Ausgaben, während der andere Thread jeweils ebenfalls Daten ausgibt. Wünschenswert wäre ein Modus, der Bitte nicht stören heißt und bedeutet, daß ein Thread exklusiv ausgeführt wird, ungestört von einem anderen Thread. Ein solcher kritischer Bereich (critic section) kann mit Hilfe eines Mutex erzeugt werden. Ein Mutex ist eine Art Fähnchen, welches derjenige Thread hochhält, der ungestört seine Arbeit verrichten will. Hinterher wird das Fähnchen wieder abgenommen, es werden wieder Störungen zugelassen.
Mutex erzeugen, entfernen
[Bearbeiten]Ein Mutex wird erzeugt, indem
SDL_mutex* SDL_CreateMutex (void);
aufgerufen wird.
Um ein Mutex wieder zu entfernen, kann
void SDL_DestroyMutex (SDL_mutex *mutex);
aufgerufen werden.
Mutex setzen
[Bearbeiten]Um mitzuteilen, daß der folgende Bereich kritisch ist und exklusiv ausgeführt wird, dient die Funktion
int SDL_mutexP (SDL_mutex *mutex);
Die Funktion gibt 0 bei Erfolg zurück, sonst -1.
P steht für "proberen" und V im folgenden Abschnitt für "verhogen", dies sind die üblichen Bezeichnungen der beiden Funktionen nach ihrem Erfinder Dijkstra. Zur besseren Lesbarkeit des Codes existiert ein Makro namens SDL_LockMutex (mutex), welches diese Funktion ersetzt.
Mutex löschen
[Bearbeiten]Nach Verlassen eines kritischen Bereiches wird die Funktion
int SDL_mutexV (SDL_mutex *mutex);
aufgerufen. Die Rückgabewerte sind dieselben wie bei SDL_mutexP. Zu dieser Funktion existiert ein Makro namens SDL_UnlockMutex (mutex).
Beipiel mit Mutex
[Bearbeiten]#include <stdlib.h> #include <SDL.h> #include <SDL_thread.h> int thread1 (void *p) { SDL_mutex *m = (SDL_mutex*) p; while (1) { SDL_LockMutex (m); printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf ("a"); SDL_Delay (10); printf("\n"); SDL_UnlockMutex (m); SDL_Delay (200); } return 0; } int thread2 (void *p) { SDL_mutex *m = (SDL_mutex*) p; while (1) { SDL_LockMutex (m); printf ("b"); SDL_Delay (10); printf ("b"); SDL_Delay (10); printf ("b"); SDL_Delay (10); printf ("b"); SDL_Delay (10); printf ("b"); SDL_Delay (10); printf("\n"); SDL_UnlockMutex (m); SDL_Delay (200); } return 0; } int main (void) { SDL_Thread *t1, *t2; SDL_mutex *mutex; if (SDL_Init (0) == -1) { printf ("Kann SDL nicht initialisieren: %s\n", SDL_GetError ()); exit (1); } mutex = SDL_CreateMutex (); t1 = SDL_CreateThread (thread1, mutex); t2 = SDL_CreateThread (thread2, mutex); SDL_Delay (2000); SDL_KillThread (t1); SDL_KillThread (t2); SDL_DestroyMutex (mutex); SDL_Quit (); return 0; }
Die Ausgabe des Programmes ist nun:
aaaaa bbbbb aaaaa bbbbb aaaaa bbbbb aaaaa bbbbb
wie gewünscht.