Thread, Sleep, Events

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Ekkehard
Beiträge: 83
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 4.0, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Thread, Sleep, Events

Beitrag von Ekkehard »

Manchmal steht man sich selber im Wege und wenn das Problem gelöst ist, wundert man sich: Warum nicht schon immer so?
Oft will man, dass ein Thread nicht 100% seiner Zeit die CPU belastet, sondern nur alle paar Millisekunden etwas macht.
Das sieht dann so aus:

Code: Alles auswählen

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    Sleep(50);
    // Mach was Nettes
  end;
end;
Im Destructor von TMyThread steht dann

Code: Alles auswählen

destructor TMyThread.Destroy;
begin
  Terminate;
  WaitFor();
  inherited Destroy;
end;
Das funktioniert wurderbar, bis man auf die Idee kommt davon nicht ein oder zwei Instanzen zu erzeugen, sondern einige Hundert. Dann stellt man (ich) fest, dass die Beendigung der vielen Threads viele Sekunden dauert.
Die Ursache liegt auf der Hand: Die Threads schlummern alle innerhalb von Sleep(50); und bekommen vom Terminate natürlich nichts mit. So vergehen pro Thread deutlich mehr als 25ms, völlig nutzlos. Natürlich könnte man in der Struktur die alle alle Thread hält, erst einen Loop machen und für jeden Thread Terminate aufrufen und dann erst Free, aber schön ist das ja auch nicht.

Was tun?

Die Lösung ist recht einfach. Statt Sleep verwendet man (ich) RTLEventWaitFor(...,50); und in Destroy den Aufruf RTLEventSetEvent(...); um die Wartezeit zu beenden. Man benötigt nur den entsprechenden Zeiger auf die Event-Daten, muss diesen in Create erzeugen und in Destroy freigeben, also ein nur sehr übersichtlicher Mehraufwand:

Code: Alles auswählen

type
  TMyThread = class(TThread)
  private
    FEvent : PRTLEvent;
  (...)
  end;

constructor TMyThread.Create(CreateSuspended: Boolean;
                       const StackSize: SizeUInt = DefaultStackSize);    
begin
  inherited Create(True); // Schlafend erzeugen
  FEvent := RTLEventCreate; // Das Event anlegen
  if not CreateSuspended then
    Suspended := False; // Ausführung beginnen, wenn gewollt
end;            

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    RTLEventWaitFor(FEvent,50); // Statt sleep. Wenn das Event nicht gesetzt wird, läuft der Aufruf in den TimeOut
    // Mach was Nettes
  end;
end;

destructor TMyThread.Destroy;
begin
  if not Terminated then
    Terminate; // Thread terminieren, falls noch nicht geschehen
  RTLEventSetEvent(FEvent); // Den Schlaf abbrechen
  if Suspended then
    Suspended := False; // Den Thread aufwecken, falls er suspendiert ist
  WaitFor(); // Auf das Ende warten
  RTLEventDestroy(FEvent); // Das Event wieder freigeben
  inherited Destroy; 
end; 
Und zack, sind alle Threads sehr zügig beendet.
(Der Code ist teilweise kopiert aber nicht in einem separaten Testprojekt getestet, möglich, dass da noch kleinere Syntaxfehler schlummern).
Grüße aus Hildesheim.

Warf
Beiträge: 2241
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Thread, Sleep, Events

Beitrag von Warf »

Es kommt erstaunlich oft vor das leute das rad neu erfinden obwohl es schon sehr gute Lösungen für gibt. Ich finde z.B. sehr oft das sich viele synchronisationsprobleme bei threads mit Barrieren lösen lassen wofür man sonst sich dumm und dämlich programmiert.

Z.b. du hast eine reihe von threads die alle ein teil Ergebnis (z.b. einen string) berechnen und dann willst du warten bis alle threads einen Arbeitsschritt durchgeführt haben, dann die Ergebnisse auf dem main thread zusammen führen und dann den nächsten Arbeitsschritt machen, ganz einfach:

Code: Alles auswählen

//Worker
while not terminated do
begin
  FBarrier^.Enter; // warten auf go signal
  //arbeitsschritt
  FBarrier.Enter; // warten bis alle anderen threads auch mit dem schritt durch sind
  end;
  
//main
While not finished do
begin
  FBarrier^.Enter; // start signal für Arbeitsschritt
  FBarrier^.Enter; // Warten bis alle threads durch sind
  // Ergebnisse zusammen führen während threads auf die nächste iteration warten
end;
// threads terminieren:
For thread in myThreads do 
  Thread.Terminate;
FBarrier^.Enter; // threads terminieren lassen

Antworten