SDL Thread Beispiel
Inhaltsverzeichnis
Multithreading in SDL
Benutzung der Synchronmöglichkeiten
Vorwort
Das Ziel des Artikel soll es sein, die Möglichkeiten zur Synchronisierung von SDL_Threads anhand von (hoffentlich) sinnvollen Beispielen zu verdeutlichen.
Als übergreifendes Beispiel wollen wir einen Webspider konstruieren.
Funktionsweise eines Webspiders
Die Funktion eines Webspiders ist recht einfach. Er lädt Webseiten herunter und analysiert sie auf weitere Links. Dies macht er solange bis keine weiteren Links mehr vorhanden sind.
Im Pseudocode:
var //Links von der Seite Links : Array of String; //Datei wo die aktuelle Seite gespeichert ist. Datei : String; i : Integer; Done : Boolean; begin Done :=False; Setlength(Links,1); Links[0] :='http://wiki.delphigl.com/index.php/Hauptseite'; i :=0; while not Done do begin Datei :=LadeHerunter(Links[i]); AnalysiereDatei(Datei); Inc(i); if i>High(Links) then Done:=True; end; end;
LadeHerunter lädt das Dokument herunter und speichert sie auf die Festplatte.
AnalysiereDatei analysiert das Dokument auf Links und fügt sie dem Array Links hinzu.
Schneckentempo
Der aktuelle Webspider funktioniert zwar, ist aber ziemlich langsam.
Weshalb?
Ganz einfach, es wird immer nur eine Datei heruntergeladen und analysiert.
Als Lösung können wir anfangen mehrere Dateien parallel herunterzuladen.
Arbeitsbeschaffungsmassnahme
Das Auslagern in einen neuen Thread ist ganz einfach:
var Thread: PSDL_Thread; begin Thread:=SDL_CreateThread(@Download,nil); end;
Der ganze Webspider sieht jetzt so aus:
var //Links von der Seite Links : Array of String; Link : String; Datei : String; Done : Boolean; //unser Thread Thread: PSDL_Thread; begin Done :=False; Setlength(Links,1); Links[0] :='http://wiki.delphigl.com/index.php/Hauptseite'; Thread :=SDL_CreateThread(@Download,nil); while not Done do begin if High(Links)=-1 then begin Done:=True; break; end; Link := Links[High(Links)]; SetLength(Links,High(Links)); Datei :=LadeHerunter(Link); AnalysiereDatei(Datei); end; //warten bis der Thread auch wirklich beendet wurde SDL_WaitThread(Thread); SDL_DestroyMutex(Muted); end; function Download: Integer; var Datei : String; Link : String; begin while not Done do begin if High(Links)=-1 then begin Done:=True; break; end; Link := Links[High(Links)]; SetLength(Links,High(Links)); Datei :=LadeHerunter(Link); AnalysiereDatei(Datei); end; end;
So, jetzt laden wir gerade 2 Dateien 'gleichzeitig' herunter.
Was passiert aber wenn die beiden Threads gleichzeitig den nächsten Links auslesen?
So würde die nächste Datei doppelt heruntergeladen und analysiert.
Wenn man das Tutorial von Lossy eX gelesen hat, weiss man ja das sowas passieren kann.
Wir brauchen also eine Kontrollstruktur die das verhindert.
Der Türsteher
In SDL ist es ein Mutex. Ein Mutex ist im Prinzip ein einfacher Boolean.
Wenn dieser False (0) ist, darf man noch nicht zugreifen.
Erst wenn er wieder freigegeben ist, darf man weitermachen.
Dazu existieren SDL_LockMutex und SDL_UnLockMutex.
SDL_LockMutex schaut erst ob der Boolean true ist, wenn nicht wartet sie, und setzt ihn dann auf falsche.
SDL_UnLockMutex setzt einfach den Wert auf true.
Der ganze Webspider sieht jetzt so aus:
var Thread: PSDL_Thread; begin Thread:=SDL_CreateThread(@Download,nil); end;
Der ganze Webspider sieht jetzt so aus:
var //Links von der Seite Links : Array of String; Done : Boolean; i : Integer; //unser Threads Thread: Array of PSDL_Thread; //der Mutex Mutex : PSDL_Mutex; begin Done :=False; Setlength(Links,1); Links[0] :='http://wiki.delphigl.com/index.php/Hauptseite'; Mutex :=SDL_CreateMutex; SetLength(Thread,2); for i:=0 to High(Thread) do Thread[i] :=SDL_CreateThread(@Download,nil); while not Done do begin SDL_Wait(500); end; //warten bis der Threads auch wirklich beendet wurde for i:=0 to High(Thread) do SDL_WaitThread(Thread[i]); SDL_DestroyMutex(Muted); end; function Download: Integer; var Datei : String; Link : String; begin while not Done do begin SDL_LockMutex(Mutex); try if High(Links)=-1 then begin Done:=True; break; end; Link := Links[High(Links)]; SetLength(Links,High(Links)); finally SDL_UnLockMutex(Mutex); end; Datei :=LadeHerunter(Link); AnalysiereDatei(Datei); end; end;
Wir haben unseren Mutex erstellt. Ab jetzt kann nur ein Thread gleichzeitig auf Links zugreifen.
Da es nur eine kurzer Zugriff ist, geht dadurch auch nicht viel Zeit verloren.
Die try...finally-Blöcke sind dafür zuständig, dass keine Deadlocks enstehen.
Soziale Verantwortung
Jetzt ist unser Webspider im Prinzip fertig. Man kann beliebig viele Threads erstellen und den Server voll auslasten.
Nur gibt es kaum eine schnellere Methode um seine IP von Phoebius für delphigl.com zu sperren, als diesen Webspider mit 100 Threads loszulassen.
Also Reduzieren wir einfach die Anzahl der gleichzeitigen Zugriffe auf den Server auf 50.
Dies heist aber nicht, dass wir die Anzahl der Threads reduzieren. Diese greifen ja nicht ständig sondern nur kurz auf den Webserver zu.
Dafür gibt es eine weitere Strukur namens Semaphore.
Diese ist kein einfacher Boolean sondern ein Integerwert. Wenn dieser grösser Null ist, darf man zugreifen, andernfalls muss man warten.
SDL stellt dafür folgende Funktionen zur Verfügung:
SDL_SemWait und SDL_SemPost.
Alles verstanden? Dann einbauen:
var //Links von der Seite Links : Array of String; Done : Boolean; i : Integer; //unser Threads Thread: Array of PSDL_Thread; //der Mutex Mutex : PSDL_Mutex; Semaphore : PSDL_Semaphore; begin Done :=False; Setlength(Links,1); Links[0] :='http://wiki.delphigl.com/index.php/Hauptseite'; Mutex :=SDL_CreateMutex; //50 ist die max. Anzahl der gleichzeitig aktiven Threads Semaphore :=SDL_CreateSemaphore(50); SetLength(Thread,100); for i:=0 to High(Thread) do Thread[i] :=SDL_CreateThread(@Download,nil); while not Done do begin SDL_Wait(500); end; //warten bis der Threads auch wirklich beendet wurde for i:=0 to High(Thread) do SDL_WaitThread(Thread[i]); SDL_DestroyMutex(Muted); SDL_DestroySemaphore(Semaphore); end; function Download: Integer; var Datei : String; Link : String; begin while not Done do begin SDL_SemWait(Semaphore); try SDL_LockMutex(Mutex); try if High(Links)=-1 then begin Done:=True; break; end; Link := Links[High(Links)]; SetLength(Links,High(Links)); finally SDL_UnLockMutex(Mutex); end; Datei :=LadeHerunter(Link); finally SDL_SemPost(Semaphore); end; AnalysiereDatei(Datei); end; end;
Finale Worte
Wir haben jetzt einen passablen kleinen Webspider gebaut (wenn ihr die Funktion AnalysiereDatei noch einbaut :p ) und hoffentlich das Konzept der Threads in SDL verstanden.
Das Erstellen und Arbeiten mit Threads ist kein Geheimnis. Es ist nur schwer etwas zu finden was man in einem Thread machen könnte ohne durch die Synchronisierung die Vorteile zu verlieren.