[gelöst]Thread in einer cli application

Rund um die LCL und andere Komponenten
Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Thread in einer cli application

Beitrag von Warf »

hubblec4 hat geschrieben:Mit Sleep() zu arbeiten um auf eine Eingabe zu warten ist gar nicht gut. In dieser Zeit kann der User nicht "Exit" machen.

War nur ein beispiel wobei man den effekt gut sehen kann, natürlich ist mit sleep auf input zu warten ziemlich dämlich (genau dafür gibts ja select und poll)
Das mit dem extra Worker-Thread für das auslesen des User-Inputs muss ich mir doch nochmal überlegen. Also ich kann besagten Thread starten und dort dann ein ReadLn() setzen, und halt solange warten bis der User was eingegeben hat und ENTER drückt.
hubblec4 hat geschrieben:Der Main.Thread kann ja dann nicht einfach mal im Worker.Thread die var Exit:=true setzen, daher das Synchronize.

Doch kann er, einen boolean auf true zu setzen ist atomar und ihn im worker auszulesen auch. Da kann also nichts schief gehen, wenn nicht atomare operationen im Spiel sind, (z.B. ein funktionsaufruf einer klasse) kannst du dafür critical sections verwenden. Die Idee dabei ist das eine CS nur von einem thread gleichzeitig betreten werden kann, bei EnterCriticalSection wird geschaut ob schon jemand drin ist, wenn ja wird gewartet bis die CS frei wird, und dann die CS betreten. Damit kann man garantieren das keine zwei threads gleichzeitig auf die selben resourcen zugreifen.
Es gibt auch bereits schon thread sichere Objekte in der RTL, z.B. die TThreadList, eine Listenimplementation die sich um das locking, etc kümmert.

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Thread in einer cli application

Beitrag von hubblec4 »

Warf hat geschrieben:
hubblec4 hat geschrieben:Doch kann er, einen boolean auf true zu setzen ist atomar und ihn im worker auszulesen auch. Da kann also nichts schief gehen, wenn nicht atomare operationen im Spiel sind, (z.B. ein funktionsaufruf einer klasse) kannst du dafür critical sections verwenden. Die Idee dabei ist das eine CS nur von einem thread gleichzeitig betreten werden kann, bei EnterCriticalSection wird geschaut ob schon jemand drin ist, wenn ja wird gewartet bis die CS frei wird, und dann die CS betreten. Damit kann man garantieren das keine zwei threads gleichzeitig auf die selben resourcen zugreifen.
Es gibt auch bereits schon thread sichere Objekte in der RTL, z.B. die TThreadList, eine Listenimplementation die sich um das locking, etc kümmert.


Wow, OK, das wusste ich nicht. Also wenn der Main.Thread die Worker-Var(eigentlich Property) Exit:=true setzt, gibt es keine Probleme wenn in dem selben Augenblick im Worker-Thread die var abgefragt wird(verändert wird sie dort eigentlich nicht)? Und das muss auch nicht in ein try-Excedpt-Block oder mit Crital-section abgefangen werden?
Gilt das nur für Booleans, oder kann da auch ein Integer oder String verändert werden?

Critical sections hatte ich auch schon bissl was drüber gelesen aber nicht weiter verfolgt. Das sollte ich dann wohl auch mal genauer anschauen.

Mir ist schon aufgefallen das ein WriteLn() bissl Zeit kostet und auch so ein Synchronize() braucht etwas Zeit. Und je öfter das ausgeführt wird um so öfter unterbricht es den Worker.Thread.

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

Re: Thread in einer cli application

Beitrag von Warf »

hubblec4 hat geschrieben:Wow, OK, das wusste ich nicht. Also wenn der Main.Thread die Worker-Var(eigentlich Property) Exit:=true setzt, gibt es keine Probleme wenn in dem selben Augenblick im Worker-Thread die var abgefragt wird(verändert wird sie dort eigentlich nicht)? Und das muss auch nicht in ein try-Excedpt-Block oder mit Crital-section abgefangen werden?
Gilt das nur für Booleans, oder kann da auch ein Integer oder String verändert werden?

Eine leseoperation ist atomar, und eine schreiboperation ist atomar (zumindest bei grunddatentypen, bei anderem weiß ichs nicht, denke aber mal nicht. Bei Integern wirds schon etwas komplizierter, z.b.

Code: Alles auswählen

for i:=0 to 1000000 do
begin
  sharedInt := sharedInt + 1;
end;

Die zweisung besteht aus zwei operationen auf sharedInt, 1. Lesen, 2. Schreiben. Wenn jetzt ein zweiter thread die variable verändert genau zwischen dem lesen und dem schreiben, geht die schreiboperation des zweiten threads verloren. Wenn du die schleife auf 4 threads ausführen würdes (mit sharedInt als globale variable), würde mit jedem durchlauf ne andere zahl rauskommen, aber wahscheinlich immer knapp unter 4000000

Strings sind viel komplizierter, die sind referenzgezählt, machen copy on write, und ganz viel anderen kram, da kann sogar richtig was kaputt gehen wenn zwei threads versuchen gleichzeitig drauf zuzugreifen.

hubblec4 hat geschrieben:Critical sections hatte ich auch schon bissl was drüber gelesen aber nicht weiter verfolgt. Das sollte ich dann wohl auch mal genauer anschauen.


Sagen wir mal du möchtest ein message system implementieren, dann könntest du sowas machen:

Code: Alles auswählen

TThread1 = class(TThread)
private
  FMessageCS: TRTLCriticalSection;
  FMessages: TStringList;
...
 
constructor TThread1.create(createSuspended: Boolean);
begin
  ...
  InitCriticalSection(FMessageCS);
  FMessages := TStringList.Create;
  ...
end;
 
destructor TThread1.Destroy;
begin
  ...
  DoneCriticalSection(FMessageCS);
  FMessages.Free;
  ...
end;
 
procedure TThread1.sendMessage(Message: String);
begin
  EnterCrtiticalSection(MessageCS);
  try
    FMessages.add(Message);
  finally
    LeaveCriticalSection(MessageCS);
  end;
end;
 
...
// Hauptschleife:
 
  EnterCrtiticalSection(MessageCS);
  try
    for message in FMessages do
      ...
    FMessages.Clear;
  finally
    LeaveCriticalSection(MessageCS);
  end;


Hat praktisch keine kosten, außer wenn zufällig addmessage aufgerufen wird wenn man in der abfrage ist (oder anders rum), dann muss der prozess der als zweites kam warten.
Problematisch wird es wenn mehrere CS und abhängigkeiten auftreten, z.B.

Code: Alles auswählen

Thread1 -> Lock CS 1
Thread2 -> Lock CS 2
Thread1 -> Lock CS 2
Thread2 -> Lock CS 1

Jetzt wartet Thread1 auf Thread2 und anders rum, ein Deadlock. Ziemlich lästig, passiert schneller als man denkt (vor allem in einem event basiertem system). Als grundregel, CS immer klein halten (als wenn man z.B. lange braucht um die messages zu verarbeiten, dann die rauskopieren, liste leeren, CS freigeben und auf der kopie arbeiten), und möglichst wenig CS auf einmal aquirieren

hubblec4 hat geschrieben:Mir ist schon aufgefallen das ein WriteLn() bissl Zeit kostet und auch so ein Synchronize() braucht etwas Zeit. Und je öfter das ausgeführt wird um so öfter unterbricht es den Worker.Thread.

Locken ist auch nicht unbedingt kostenlos, vor allem wenn sehr oft versucht wird zuzugreifen kann es sein das du länger im CS wartest als du tatsächlich arbeitest (dann wirds durch das lock zwangsserialisiert), kommt immer auf das problem an, parallele programierung ist leider nicht einfach.
WriteLn besteht halt aus ein paar operationen, zum einen wird in eine datei geschrieben, was bedeutet der aktuelle prozess wird angehalten, der kernel übernimmt die schreiboperation, der prozess wird fortgesetzt, dann wird geflusht, dafür wird der prozess wieder angehalten, der kernel schreibt seinen dateisystem buffer in die pipe, und erst wenn das erfolgreich war ist die writeln operation zu ende. Wenn du z.B. einen bruteforcer schreibst und dir jedes zwischenergebnis ausgeben lässt, kann dich das gut nen faktor 1000 an geschwindigkeit kosten

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: [gelöst]Thread in einer cli application

Beitrag von hubblec4 »

Habe das Thema mal als gelöst markiert.

Mein tool ist soweit fertig und ich überlege es open source zu machen, da ich finde es gibt zu wenig Lazarus open source tools.
Gibt es einen speziellen Platz für so ein Vorhaben oder ist einfach GitHub das unkomlizierteste.

Ich habe das Projekt mal hier angehängt.
Dateianhänge
hSplit.zip
(14.1 KiB) 86-mal heruntergeladen

Antworten