Delphi-PRAXiS
Seite 1 von 4  1 23     Letzte »    

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren? (https://www.delphipraxis.net/191510-zugriffe-auf-objekt-aus-mehreren-threads-wie-richrig-synchronisieren.html)

uups 23. Jan 2017 11:49

Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Hallo!

Ich habe in meinem Programm eine globale Instanz eines Threads laufen mit einer TObjectList drin. Bei dem Thread handelt es sich um eine Art Verarbeitungs-Queue, in der Aufgaben in Form von Items abgelegt und vom Thread dann verarbeitet werden. Auf die Thread-Instanz greifen mehrere andere Threads zu und zwar ausschließlich mit dem Ziel, der TObjectList neue Objekte (Aufgaben) hinzuzufügen. Dafür ist eine public-deklarierte Funktion innerhalb des Threads vorgesehen.

Reicht es in diesem Fall aus, wenn ich nur die TObjectList mit einer TCriticalSection (beide innerhalb der Objekt-Instanz deklariert) schütze oder sollte ich bereits die Zugriffe auf die eingenliche Thread-Instanz mit einer global deklarierten TCriticalSection synchronisieren?

Als ich die Klasse erstellt habe, habe ich mir gedacht, dass auf die eigentliche Thread-Instanz eher nur lesend zugegriffen wird und erst die TObjectList geschützt werden sollte. Ist es überhaupt richtig, die Liste innerhalb eines Threads zu erstellen und die dann mithilfe eier public-Funktion zu beschreiben oder wäre es besser, die Liste und die CriticalSection als globale Objekte zu deklarieren und dem Thread nur die Abarbeitung der Aufgaben anzuvertrauen?

Aktuell sieht es so aus bei mir:

Delphi-Quellcode:
type
  TapDataReceiverQueueItem = class
  private
    FClientData: string;
    FTarget: TapDataTarget;
    FPriority: TapPriority;
  public
    property ClientData: string read FClientData write FClientData;
    property Target: TapDataTarget read FTarget write FTarget;
    property Priority: TapPriority read FPriority write FPriority;
end;

type
  TapDataReceiverQueue = class(TThread)
  constructor Create(CreateSuspended: Boolean);
  destructor Destroy; override;
  private
    FQueue: TObjectList;
    FcsQueue: TCriticalSection;
  protected
    procedure Execute; override;
    procedure DeleteFromQueue(AIndex: integer);
  public
    procedure AddToQueue(AClientData: string; ATarget: TapDataTarget; APriority: TapPriority);
    function Count: integer;
end;

var
  DataReceiverQueue: TapDataReceiverQueueItem;

implementation

...

constructor TapDataReceiverQueue.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  NameThreadForDebugging(ClassName, ThreadID);
  FQueue := TObjectList.Create(true);
  FcsQueue := TCriticalSection.Create;
end;

destructor TapDataReceiverQueue.Destroy;
begin
  if Assigned(FQueue) then FreeAndNil(FQueue);
  if Assigned(FcsQueue) then FreeAndNil(FcsQueue);
  DataReceiverQueue := nil;
  inherited Destroy;
end;

procedure TapDataReceiverQueue.Execute;
var
  QueueItem: TapDataReceiverQueueItem;
begin
  while not Terminated do
  begin
    if (Count > 0) then
    begin
      if PERF_DATA_RECEIVER_THREADS_COUNT < PERF_DATA_RECEIVER_THREADS_MAX_VALUE then
      repeat
        try
          if Assigned(FQueue.Items[0]) then
          try
            QueueItem := TapDataReceiverQueueItem(FQueue.Items[0]);
            TapDataReceiver.Create(false, QueueItem.ClientData, QueueItem.Target, QueueItem.Priority);
          finally
            DeleteFromQueue(0);
          end;
        except
        end;
      until (Terminated) or (Count = 0) or (PERF_DATA_RECEIVER_THREADS_COUNT >= PERF_DATA_RECEIVER_THREADS_MAX_VALUE);
    end;
    Delay(1);
  end;
end;

procedure TapDataReceiverQueue.AddToQueue(AClientData: string; ATarget: TapDataTarget; APriority: TapPriority);
var
  Item: TapDataReceiverQueueItem;
begin
  Item := TapDataReceiverQueueItem.Create;
  Item.ClientData := AClientData;
  Item.Target := ATarget;
  Item.Priority := APriority;

  FcsQueue.Enter;
  try
    FQueue.Add(Item);
  finally
    FcsQueue.Leave;
  end;
end;

procedure TapDataReceiverQueue.DeleteFromQueue(AIndex: integer);
begin
  FcsQueue.Enter;
  try
    FQueue.Delete(AIndex);
  finally
    FcsQueue.Leave;
  end;
end;

function TapDataReceiverQueue.Count: integer;
begin
  Result := FQueue.Count;
end;

a.def 23. Jan 2017 12:17

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Ich habe ein ähnliches Szenario wie du und bin immer gut mit
Delphi-Quellcode:
TThread.Queue()
klar gekommen.
Soweit ich weiß legt Queue alles im Stack ab und dann wird der Stack nach und nach abgearbeitet.

uups 23. Jan 2017 12:49

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Zitat:

Zitat von a.def (Beitrag 1359691)
Ich habe ein ähnliches Szenario wie du und bin immer gut mit
Delphi-Quellcode:
TThread.Queue()
klar gekommen. Soweit ich weiß legt Queue alles im Stack ab und dann wird der Stack nach und nach abgearbeitet.

Das schon. Allerdings werden in meinem Fall mehrere Aufgaben gleichzeitig gestartet, bis die zuvor festgelegte (für jedes System individuell errechnete) Maximalzahl von gleichzeitig zu ausführenden Aufgaben erreicht ist. Im Fall mit
Delphi-Quellcode:
TThread.Queue()
wird der Stack sequentiell verarbeitet.

a.def 23. Jan 2017 12:53

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Ok daran habe ich nicht gedacht.
Demnach bringen mehrere Threads mit derselben Aufgabe rein gar nicht, wenn man mit Queue alles in den Stack legt.
Dann müssen wohl die Profis ran :stupid:

Andererseits... du fügst doch nur Einträge in eine ObjectList hinzu.
Die eigentliche Arbeit findet doch woanders statt. Dann sollte Queue doch eigentlich doch kein Problem sein.

Klaus01 23. Jan 2017 12:56

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
.. eventuell magst Du ja auch die TObjectList mit der TThreadList austauschen?

Grüße
Klaus

uups 23. Jan 2017 13:01

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Zitat:

Zitat von a.def (Beitrag 1359696)
Die eigentliche Arbeit findet doch woanders statt...

So ist es. Der Thread startet für jede Aufgabe einen neuen Thread, der die eingentliche Verarbeitung der Daten übernimmt. Die Queue ist genau dafür da, um die Anzahl dieser Verarbeitungsthreads nicht zu überschreiten und damit das System zu überlasten.

Zitat:

Zitat von a.def (Beitrag 1359696)
.. Dann sollte Queue doch eigentlich doch kein Problem sein.

Doch, denn die Aufrufe der Verarbeitungsthreads landen dann immer noch im Stack.

Zacherl 23. Jan 2017 13:04

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Zitat:

Zitat von uups (Beitrag 1359698)
So ist es. Der Thread startet für jede Aufgabe einen neuen Thread, der die eingentliche Verarbeitung der Daten übernimmt.

Schau dir mal
Delphi-Quellcode:
TThreadPool
an. Der ist genau für solche Zwecke gedacht. Du kannst dort einen
Delphi-Quellcode:
MaxThreadCount
einstellen und Tasks queuen. Der Pool arbeitet dann alle Aufgaben ab, aber erstellt nie mehr gleichzeitige Threads als angegeben (ist btw. auch deutlich performanter, da Thread Creation zumindest unter Windows einen ziemlichen Overhead hat und der ThreadPool die Threads wiederverwendet).

uups 23. Jan 2017 13:24

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Zitat:

Zitat von Zacherl (Beitrag 1359699)
Schau dir mal
Delphi-Quellcode:
TThreadPool
an. Der ist genau für solche Zwecke gedacht. Du kannst dort einen
Delphi-Quellcode:
MaxThreadCount
einstellen und Tasks queuen. Der Pool arbeitet dann alle Aufgaben ab, aber erstellt nie mehr gleichzeitige Threads als angegeben (ist btw. auch deutlich performanter, da Thread Creation zumindest unter Windows einen ziemlichen Overhead hat und der ThreadPool die Threads wiederverwendet).

Klingt toll. Wenn ich richtig recherchiert habe, ist der TThreadPool ein Teil der neuen Parallel Library, oder? Heisst das etwa, ich muss mich dann nur um die Erstellung eines neuen Tasks kümmern und den Rest der geschichte vergessen?

Es gibt ein s.g. vordefinierter DefaultPool. Von dem kann man die Zahl der Threads abrufen, die problemlos gleichzeitig laufen können. Gilt dieser Wert für das gesammte System oder nur für die aktuelle Anwendung? Ist es sinnvoll, einen eingenen ThreadPool zu erstellen?

uups 23. Jan 2017 13:43

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Zitat:

Zitat von Klaus01 (Beitrag 1359697)
.. eventuell magst Du ja auch die TObjectList mit der TThreadList austauschen?

Könnte man machen, aber dabei erübrigt sich lediglich die TCriticalSection für den Zugriff auf TObjectList. Die eigentliche Frage bleibt aber: reicht allein die Absicherung der TObjectList oder muss ich auch noch den Zugriff auf den Thread mittels
Delphi-Quellcode:
AddToQueue()
oder
Delphi-Quellcode:
Count()
absichern?

a.def 23. Jan 2017 13:45

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Count ist doch nur lesend. Ich denke da muss man nix absichern.


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:20 Uhr.
Seite 1 von 4  1 23     Letzte »    

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz