AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Algorithmen, Datenstrukturen und Klassendesign Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Thema durchsuchen
Ansicht
Themen-Optionen

Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

Ein Thema von norwegen60 · begonnen am 7. Nov 2019 · letzter Beitrag vom 18. Nov 2019
Antwort Antwort
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
511 Beiträge
 
Delphi 12 Athens
 
#1

Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 11:18
Hallo zusammen,

ich bin noch nicht ganz ThreadSave arbeite aber daran. Nach dem Lesen einiger Themen habe ich noch eine Verständnisfrage.

Schützt eine CriticalSection den kompletten Thread oder kann die CS auch auf den Zugriff auf eine Property runter gebrochen werden?
In etwa so:
Delphi-Quellcode:
type
  TMyThread = class(TThread)
  private
    FCount:Integer;
    FProp1: Real;
    FProp2: Real;

    FEventProp1: TThreadReal;
    FEventProp2: TThreadReal;

    FSectionProp1: TCriticalSection; // CriticalSection für Prop1
    FSectionProp2: TCriticalSection; // CriticalSection für Prop2

    procedure SyncEventProp1;
    procedure SyncEventProp2;

    function GetProp1: Real;
    function GetProp2: Real;

    procedure SetProp1(const Value: Real);
    procedure SetProp2(const Value: Real);
  protected
    procedure Execute; override;
  public
    property Prop1: Real read GetProp1 write SetProp1;
    property Prop2: Real read GetProp2 write SetProp2;

    property EventProp1: TThreadReal read FEventProp1 write FEventProp1;
    property EventProp2: TThreadReal read FEventProp2 write FEventProp2;

    constructor Create(sEinst: String);
    destructor Destroy; override;
  end;

implementation


constructor TMyThread.Create(sEinst: String);
begin
  inherited Create(true);

  FSectionProp1 := TCriticalSection.Create;
  FSectionProp2 := TCriticalSection.Create;
  FCount := 0;
end;

destructor TMyThread.Destroy;
begin
  FSectionProp1.Free;
  FSectionProp2.Free;

  inherited Destroy;
end;


procedure TMyThread.Execute;
begin
  inherited;

  while not Terminated do
  begin
    if Odd(FCount) then
      Prop1 := FCount
    else
      Prop2 := FCaount;
    FCount := FCount + 1;
    Sleep(50);
  end;
end;

function TMyThread.GetProp1: Real;
begin
  FSectionProp1.Acquire;
  try
    Result := FProp1;
  finally
    FSectionCount.Release;
  end;
end;

function TMyThread.GetProp1: Real;
begin
  FSectionProp1.Acquire;
  try
    Result := FProp1;
  finally
    FSectionCount.Release;
  end;
end;


procedure TMyThread.SetProp1(const Value: Real);
begin
  FSectionProp1.Acquire;
  try
    if FProp1 <> Value then // Hier bewusst FProp1 und nicht Prop1, da Property ja schon geschützt
    begin
      FProp1 := Value;
      Synchronize(SyncEventProp1); // Auch das in CS und damit Ausgabe geschützt
    end;
  finally
    FSectionProp1.Release;
  end;
end;

procedure TMyThread.SetProp2(const Value: Real);
begin
  FSectionProp2.Acquire;
  try
    if FProp2 <> Value then // Hier bewusst FProp2 und nicht Prop2, da Property ja schon geschützt
    begin
      FProp2 := Value;
      Synchronize(SyncEventProp2); // Auch das in CS und damit Ausgabe geschützt
    end;
  finally
    FSectionProp2.Release;
  end;
end;

procedure TMyThread.SyncEventProp1;
begin
  if Assigned(FEventProp1) then
    FEventConnected(FProp1); // Hier bewusst FProp1 und nicht Prop1, da Property ja schon durch SetProp1 geschützt und Sync nur von dort geschieht
end;

procedure TMyThread.SyncEventProp2;
begin
  if Assigned(FEventProp2) then
    FEventConnected(FProp2); // Hier bewusst FProp1 und nicht Prop1, da Property ja schon durch SetProp1 geschützt und Sync nur von dort geschieht
end;

end.
Im Main-Thread sollte nun Event per Syncchronize oder Zugriff auf Property möglich sein
Delphi-Quellcode:
procedure TMain.FormCreate(Sender: TObject);
begin
  MyThread.EventProp1 := EventProp1;
end;

procedure TMain.EventProp1(Value: Real);
// Ständige Aktualisierung
begin
  Label1.Caption := format('%3.2f', [Value]);
end;

procedure TMain.Button1Click((Sender: TObject);
begin
  Label2.Caption := format('%3.2f', [MyThread.Prop1]);
  Label3.Caption := format('%3.2f', [MyThread.Prop2]);
end;
Damit könnte theoretisch jemand auf Prop1 zugreifen währen der Thread in Prop2 schreibt

Ich habe hier bewusst eine Mischung aus Sychronice und Aufruf der Variablen gewählt um auch zu erfahren ob diese gemischte Verwendung Threadsafe implementiert wäre.

Bisher habe ich CS verwendet, habe aber beim erneuten Suchen gesehen, dass TMultiReadExclusiveWriteSynchronizer vielleicht besser wäre. Frage nach unterschiedlichen Blöcken gilt deshalb analog für TMultiReadExclusiveWriteSynchronizer (msProp1, msProp2)

Danke für euer Feedback
Gerd
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
4.164 Beiträge
 
Delphi 12 Athens
 
#2

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 12:17
TMultiReadExclusiveWriteSynchronizer würde ich da machen wo wirklich mehr gelesen and geschrieben wird.

Aber Vorsicht wenn du FMX einsetzt:
Unter FMX-Mobile wird das nur als normaler TMonitor implementiert (also keine MultiRead...).
Müsste ich jetzt nachsehen ob Android/iOS oder Beide.
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.191 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 12:30
Das sind im Endeffekt ja viele verschiedene Fragen auf einmal.

Ich denke bei den wenigstens Anwendungen ist das wirklich interessant ob im Hintergrund eine TCriticalSection, ein TMonitor, ein TMultiReadSonstwas zum Einsatz kommt. Die paar Nanosekunden sparen wir uns an dieser Stelle am besten vorerst...

Ich finde das ziemlich kompliziert gelöst. Erstens finde ich es fehl am Platz dass der Thread sich darum kümmern muss hier etwas in den Hauptthread zu pushen. Der Thread kann ein Event anbieten, das Formular registriert sich dort. Wenn das Formular es braucht dass es im Hauptthread ausgeführt wird, macht es das bitte selbst.

Wie du schon bemerkt hast ist ein Riesenproblem dass der Event-Handler ausgeführt wird während der Zugriff noch gesperrt ist. Wenn dein Formular jetzt z.B. den Wert noch einmal lesen will hängt alles - Weil deine TMyThread-Instanz hat es noch gesperrt. Folglich:
  1. Zugriff sperren
  2. Wert setzen, merken ob vorher anders war
  3. Sperre aufheben
  4. Ist der Wert vorher anders gewesen? Change-Event auslösen

Zweitens würde ich mir verschiedene Events sparen - Vielleicht ist bei mir die Welt viel einfacher, aber in 90 % der Fälle will man wirklich nur darauf reagieren dass sich etwas geändert hat und pinselt dann die Oberfläche neu. Sicher könnte man etwas Compiler-Strom sparen das alles haarklein aufzuteilen, aber im ersten Wurf braucht man das sicher nicht.

Eine kleine Änderung die ich mir noch erlaubt habe ist dass ich dem Thread nicht zwei oder mehr Werte direkt geben würde - Lager die Werte nochmal in eine einfache Struktur aus, einen Record (also Wertetyp).

Das sähe am Schluss dann so aus:

Delphi-Quellcode:
unit Unit2;

interface uses
   System.Classes,
   System.SyncObjs;

type
   TData = record
      Prop1:   Real;
      Prop2:   Real;
    end;

   TDataChangeEventHandler = procedure(const data: TData) of object;

   TMyThread = class(TThread)
      private var
         handler:   TDataChangeEventHandler;
         FCount:      Integer;
      private
         procedure incCounter(); inline;
      protected var
         data: TData;
         mutex: TCriticalSection;
      protected
         procedure Execute(); override;
         procedure setProp1(const value: Real);
         procedure setProp2(const value: Real);
         procedure invokeChangeEvent(); inline;
      public
         constructor Create(CreateSuspended: Boolean);
         destructor Destroy(); override;

         procedure setOnChange(const handler: TDataChangeEventHandler);
         function getOnChange(): TDataChangeEventHandler;
         function getData(): TData;
   end;

implementation uses System.SysUtils;

{ TMyThread }

constructor TMyThread.Create(CreateSuspended: Boolean);
begin
   inherited Create(CreateSuspended);
   mutex := TCriticalSection.Create();
end;

destructor TMyThread.Destroy();
begin
   mutex.Free();
   inherited;
end;

procedure TMyThread.Execute();
begin
   while (not Terminated) do
      begin
         if Odd(FCount) then
            setProp1(FCount)
         else
            setProp2(FCount);

         incCounter();
         TThread.Sleep(50);
      end;
end;

function TMyThread.getData(): TData;
begin
   mutex.Acquire();
   try
      Result := data;
   finally
      mutex.Release();
    end;
end;

function TMyThread.getOnChange(): TDataChangeEventHandler;
begin
   mutex.Acquire();
   try
      Result := handler;
   finally
      mutex.Release();
    end;
end;

procedure TMyThread.incCounter();
begin
   if (FCount < Integer.MaxValue) then
      Inc(FCount)
   else
      FCount := Integer.MinValue;
end;

procedure TMyThread.invokeChangeEvent();
var
   _handler: TDataChangeEventHandler;
begin
   _handler := getOnChange();
   if Assigned(_handler) then
      _handler(data);
end;

procedure TMyThread.setOnChange(const handler: TDataChangeEventHandler);
begin
   mutex.Acquire();
   try
      self.handler := handler;
   finally
      mutex.Release();
   end;
end;

procedure TMyThread.setProp1(const value: Real);
var
   hasChanged: Boolean;
begin
   mutex.Acquire();
   try
      hasChanged := (data.Prop1 <> value);
      if hasChanged then
         data.Prop1 := value;
   finally
      mutex.Release();
   end;

   if hasChanged then
      invokeChangeEvent();
end;

procedure TMyThread.setProp2(const value: Real);
var
   hasChanged: Boolean;
begin
   mutex.Acquire();
   try
      hasChanged := (data.Prop2 <> value);
      if hasChanged then
         data.Prop2 := value;
   finally
      mutex.Release();
   end;

   if hasChanged then
      invokeChangeEvent();
end;

end.
  Mit Zitat antworten Zitat
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
511 Beiträge
 
Delphi 12 Athens
 
#4

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 13:15
Vielen Dank für das schnelle Feedback. Ein paar Dinge habe ich schon umgesetzt.

Vielleicht kurz beschrieben, an was ich gerade dran bin.
Universelle ComPort-Ansteuerung die je nach Gerät ein oder zwei Befehle automatisch (pollend) schickt und zusätzlich die Möglichkeit hat, zusätzliche Befehle vom Main entgegen zu nehmen, zu senden und Antwort zurück zugeben.
Da die Befehle immer seriell gesendet werden, kam das mit zwei Events für Prop1 (z.B. Position) und Prop2 (z.B. Speed) auf.
Z.B. wird in einer Anwendung aufgrund der Position ein Motor abgeschalten. => Position wird öfter abgefragt als Speed und im Event des Mains entsprechend ausgewertet. Ist aber kein Killer für Rückgabe als Record.

Hier kurz zusammengefasst wie ich es verstanden habe
  • Eigene CriticalSection machen keinen Sinn
  • Zeiteinsprungen zwischen CriticalSection und TMultiReadExclusiveWriteSynchronizer vernachlässigbar
  • Werte in Klasse oder Record zusammenfassen kann Handling vereinfachen da nur ein Event-Handler im Main nötig

Die Bemerkung
Zitat:
Wie du schon bemerkt hast ist ein Riesenproblem dass der Event-Handler ausgeführt wird während der Zugriff noch gesperrt ist. Wenn dein Formular jetzt z.B. den Wert noch einmal lesen will hängt alles - Weil deine TMyThread-Instanz hat es noch gesperrt.
habe ich nicht ganz verstanden. Falls sich der auf meinen Zusatz bezieht
Zitat:
Damit könnte theoretisch jemand auf Prop1 zugreifen währen der Thread in Prop2 schreibt
hatte ich den als Chance gesehen. Ich dachte durch die zwei Sections ist es möglich, dass der Main auf Prop1 zugreigt und der Thread in Prop2 schreibt ohne dass sich die ins Gehege kommen. Sehe ich das falsch?
Wo ist bei mir die Sicherheitslücke?
  Mit Zitat antworten Zitat
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
511 Beiträge
 
Delphi 12 Athens
 
#5

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 13:19
Und noch eine Frage hierzu
Delphi-Quellcode:
procedure TMyThread.invokeChangeEvent();
var
   _handler: TDataChangeEventHandler;
begin
   _handler := getOnChange();
   if Assigned(_handler) then
      _handler(data);
end;
Warum der Zwischenschritt über _handler? Warum nicht so?
Delphi-Quellcode:
procedure TMyThread.invokeChangeEvent();
begin
   if Assigned(getOnChange) then
      getOnChange(data);
end;
Gewohnheit oder begründet?
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.191 Beiträge
 
Delphi 10 Seattle Enterprise
 
#6

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 13:37
Weil es hochtheoretisch sein könnte dass jemand in der Zwischenzeit zwischen "if" und "then" aus einem anderen Thread setOnChange(..) aufruft und einen anderen Handler (oder garkeinen) setzt. Dann würdest du im "then"-Teil einen leeren Funktionszeiger aufrufen wollen und es gäbe eine Zugriffsverletzung.

Könnte man das OnChange nicht mittendrin ändern (z.B. würde es nur einmal im Konstruktor des Threads übergeben werden) müsste man sich davor nicht fürchten und man bräuchte es nicht und könnte einfach sagen
Delphi-Quellcode:
   if Assigned(getOnChange) then
      getOnChange(data);
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.191 Beiträge
 
Delphi 10 Seattle Enterprise
 
#7

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 13:51
habe ich nicht ganz verstanden. (...)
Wo ist bei mir die Sicherheitslücke?
Was ich meinte: Angenommen wir nutzen unsere Sperre nicht nur für das Setzen des Wertes sondern auch führ das Ausführen der Events, also z.B. der Aktualisierung deines Formulars. Aus
Delphi-Quellcode:
Sperre.Betreten()
try
   wert = neuer wert
finally
   Sperre.Verlassen()
end;
wird also

Delphi-Quellcode:
Sperre.Betreten()
try
   wert = neuer wert
   Wenn der Wert sich geändert hat:
      Führe OnChange aus
finally
   Sperre.Verlassen()
end;
Und dein "OnChange" ist gleichzeitig auch noch dass wir mit TThread.Synchronize(..) hantieren (davon hatte ich ja stark abgeraten).

Dann kann es sein dass du in deiner Ereignisbehandlung (also auf deinem Formular) einen Getter oder Setter vom Thread aufrufst. Niemand kann dich daran hindern. Nur was passiert dann? Er will wieder sperren. Und darauf wird er ewig warten, denn der Thread hält es noch gesperrt und wartet darauf dass der TThread.Synchronize(..) fertig wird. Der wird aber nie fertig, da er auf die Sperre wartet. Eine Verklemmung auf ewig.

Das meinte ich.

Weiterhin ist es denke ich unnötig dass der Thread so lange pausiert und wartet bis jemand darauf reagiert hat. Deine serielle Kommunikation würde nun so lange stehen bis das Gepinsel auf dem Formular fertig ist.
  Mit Zitat antworten Zitat
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
511 Beiträge
 
Delphi 12 Athens
 
#8

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 14:09
OK, weitestgehend verstanden (Hoffe ich)

Rein theoretisch und vor allem für mein Verständnis:
  1. TMultiReadExclusiveWriteSynchronizer würde das Problem beseitigen da hier mehrer Lese-Zugriffe möglich wären und diese Werte nie vom Main gesetted werden?
  2. Eigene Section für jedes Property würden das Problem verringern, da es nur beim Zugriff auf dieselbe Prop klemmen würde?
  3. Hängt der Thread nicht auch bei deinem invokeChangeEvent bis sich die GUI gepinselt hat?
  4. Und müsste das, wenn separat verschickt, nicht auch in eine CS da ja sonst Main und Thread auf data zugreifen könnten?

Delphi-Quellcode:
procedure TMyThread.invokeChangeEvent();
var
   _handler: TDataChangeEventHandler;
begin
   _handler := getOnChange();
   if Assigned(_handler) then
      _handler(data);
end;
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.191 Beiträge
 
Delphi 10 Seattle Enterprise
 
#9

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 11. Nov 2019, 10:03
  1. Ich denke der TMultiReadExclusiveWriteSynchronizer ist eine angemessene Lösung wenn man wirklich die Aufgabenstellung hat wenn ein Wert über mehrere Threads abgesichert werden muss und hierbei sehr oft gelesen, aber nur selten geschrieben wird. Klar war dein Beispiel konstruiert, aber das Sleep(50) ließ jetzt auf eine eher häufige Änderung schließen. Ein 100%iges Versprechen Deadlocks zu vermeiden kann dir auch ein TMultiReadExclusiveWriteSynchronizer nicht geben, er bringt nur in speziellen Anwendungsfällen wohl bessere Performance.
  2. Ja, aber ob der exorbitant höhere Schreibaufwand und erhöhte Komplexität es wert ist dass das ursprüngliche Problem nur etwas verringert wurde?

Zu Punkt 3 & 4: Da hast du Recht. Ich hatte nur das Lesen auf dem Schirm, aber hochtheoretisch kann jemand setProp1(..) aufrufen, und vor der letzten Zeile wird der Thread angehalten und von einem anderen wird setProp2(..) aufgerufen. Der läuft komplett durch und später wird setProp1(..) noch ordentlich zu Ende gebracht. Keine Datenkorruption, aber das OnChange-Event wurde dann einfach zwei mal mit den neuen Werten aufgerufen.


Seit der PPL denken plötzlich alle, Multithreading und parallel programming sei nen Kindergeburtstag
Aus den Slides meines Vortrags auf den Delphi-Tagen 2013:

Zitat von Danny Thorpe:
New programmers are drawn to multithreading like moths to flame, with similar results.
  Mit Zitat antworten Zitat
Antwort Antwort

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 16:12 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz