SDL Thread Beispiel

Aus DGL Wiki
Wechseln zu: Navigation, Suche

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.