Delphi-PRAXiS

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.

Zacherl 23. Jan 2017 14:08

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

Zitat von uups (Beitrag 1359702)
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?

Exakt :) Du fütterst den Pool mit Tasks und der Rest sollte dann automatisch ablaufen.

Zitat:

Zitat von uups (Beitrag 1359702)
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?

Wenn ich das richtig in Erinnerung habe, skaliert der Default-Pool die maximale Anzahl an Threads anhand der CPU Spezifikation (Anzahl der Kerne) und der CPU Auslastung, also ist eher ein globaler Wert. Für deinen Anwendungsfall würde ich keinen seperaten Pool erstellen.

uups 23. Jan 2017 14:26

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

Zitat von Zacherl (Beitrag 1359725)
Exakt :) Du fütterst den Pool mit Tasks und der Rest sollte dann automatisch ablaufen.

Gibt es auch eine Möglichkeit, bestimmte Tasks zu priorisieren?

In meinem Fall hat die Verarbeitung der eingehenden Daten eine höhere Priorität, als andere zweitrangige Aufgaben, die sich eventuell erst während der Verarbeitung ergeben. Für diese zweitrangige Aufgaben existieren nähmlich auch Queue-Threads, die ihre Aufgaben mit einer niedriegeren Priorität abarbeiten.

Klaus01 23. Jan 2017 14:51

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

Zitat von uups (Beitrag 1359705)
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?

Das eine CriticalSection im Object (in der ObjectListe) vorhanden ist, sorgt ja nicht dafür das sie threadsafe ist.
Das wird durch "enter" und "leave" sichergestellt.
Mit der ThreadList müsstest Du dich nicht mehr darum zu kümmern zugriffe auf die Liste abzusichern.
Das macht die Klasse dann automatisch.

Grüße
Klaus

a.def 23. Jan 2017 14:53

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
A propos Zugriffe.

Muss man nur schreibende Zugriffe mit einer CriticalSection absichern oder auch lesende?

Klaus01 23. Jan 2017 14:56

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

Zitat von a.def (Beitrag 1359736)
A propos Zugriffe.

Muss man nur schreibende Zugriffe mit einer CriticalSection absichern oder auch lesende?

ich würde auch lesende absichern.
Denn ich denke es wird krachen wenn ein Listenelemet gelesen wird und ein anderer Thread löscht im gleichen Zeitraum diese Element.

Grüße
Klaus

uups 23. Jan 2017 14:58

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

Zitat von a.def (Beitrag 1359736)
Muss man nur schreibende Zugriffe mit einer CriticalSection absichern oder auch lesende?

Ich würde spontan sagen, sowohl als auch. Anderfalls könnte ich mir die Existenz des sehr langsamen
Delphi-Quellcode:
TMultipleReadExclusiveWriteSynchronizer
nicht erklären können.

stahli 23. Jan 2017 15:03

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Man muss alle gleichzeitigen Zugriffe absichern, die nicht atomar sind, sobald ein Prozess ggf. etwas schreibt.
Übrigens: auch ein
Delphi-Quellcode:
I := J
oder ein
Delphi-Quellcode:
Inc(I)
ist - auf Prozessorebene - nicht atomar.

Wenn man gemeinsame Zugriffe nicht korrekt absichert, kann man von falschen Werten bis Programmabstürzen alles erhalten.

Zacherl 23. Jan 2017 15:11

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

Zitat von uups (Beitrag 1359730)
Zitat:

Zitat von Zacherl (Beitrag 1359725)
Exakt :) Du fütterst den Pool mit Tasks und der Rest sollte dann automatisch ablaufen.

Gibt es auch eine Möglichkeit, bestimmte Tasks zu priorisieren?

Leider nicht :? Zumindest nicht, als ich das letzte Mal einen Blick drauf geworfen habe. Tatsächlich habe ich mir dann beholfen, indem ich zwei ThreadPools manuell erstellt habe, wobei Der mit den priorisierten Tasks 10 gleichzeitig abarbeiten durfte, während der andere Pool auf Zwei beschränkt war. Ist natürlich auch nicht wirklich ideal.

uups 23. Jan 2017 15:26

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

Zitat von Zacherl (Beitrag 1359741)
Leider nicht :? Zumindest nicht, als ich das letzte Mal einen Blick drauf geworfen habe.

Scheint sich bis jetzt auch nicht geändert zu haben.

Zitat:

Zitat von Zacherl (Beitrag 1359741)
Tatsächlich habe ich mir dann beholfen, indem ich zwei ThreadPools manuell erstellt habe, wobei Der mit den priorisierten Tasks 10 gleichzeitig abarbeiten durfte, während der andere Pool auf Zwei beschränkt war. Ist natürlich auch nicht wirklich ideal.

Genauso handle ich jetzt auch mit den unterschiedlichen Queues. Dabei habe ich aber noch die Möglichkeit, mit
Delphi-Quellcode:
TThreadPriority
die Priorität des jeweiligen Queue-Threads anzupassen. Es gibt aber oft geteilte Meinung, dass dies auch nicht die beste Lösung ist.

Es hat mich auch interessiert, wie ich einem TTask meine Parameter übergeben kann. Dabei bin ich auf diesen Post gestossen, verstehe aber nicht ganz, wieso im Beispiel von Sir Rufo die Parameter der Funktion mit
Delphi-Quellcode:
const
übergeben werden. Muss das allgemein so sein?

a.def 23. Jan 2017 15:35

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

Zitat von uups (Beitrag 1359743)
Es hat mich auch interessiert, wie ich einem TTask meine Parameter übergeben kann. Dabei bin ich auf diesen Post gestossen, verstehe aber nicht ganz, wieso im Beispiel von Sir Rufo die Parameter der Funktion mit
Delphi-Quellcode:
const
übergeben werden. Muss das allgemein so sein?

Wenn ich mich irre steinigt mich aber ich habe irgendwo mal gelesen, dass das Übergeben von Objekten mit const in der Funktion selber dann nur ein Zeiger auf das Object ist und bei Variablen wird wohl eine Kopie der Variablen erstellt.

Wenn es wirklich eine Kopie ist, das mit dem Thread ja kein Problem mehr denn die ursprüngliche Variable wird nicht angefasst.

Zacherl 23. Jan 2017 15:54

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

Zitat von uups (Beitrag 1359743)
Es hat mich auch interessiert, wie ich einem TTask meine Parameter übergeben kann. Dabei bin ich auf diesen Post gestossen, verstehe aber nicht ganz, wieso im Beispiel von Sir Rufo die Parameter der Funktion mit
Delphi-Quellcode:
const
übergeben werden. Muss das allgemein so sein?

Das
Delphi-Quellcode:
const
ist tatsächlich nicht zwingend erforderlich, sondern nur eine Performance-Optimierung (
Delphi-Quellcode:
const
und
Delphi-Quellcode:
var
werden intern [meistens] als Pointer umgesetzt, wie a.def korrekterweise angemerkt hat). Im Falle von String bewirkt es vor allem, dass die automatische Referenzzählung deaktiviert und grade KEINE lokale Kopie erzeugt wird.

uups 23. Jan 2017 22:17

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

Zitat von Zacherl (Beitrag 1359746)
Im Falle von String bewirkt es vor allem, dass die automatische Referenzzählung deaktiviert und grade KEINE lokale Kopie erzeugt wird.

Wird die Referenzzählung wirklich deaktiviert oder wird es der ARC nur mitgeteilt, dass die String-Variable noch woanders benötigt wird?

Zacherl 23. Jan 2017 23:30

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

Zitat von uups (Beitrag 1359774)
Zitat:

Zitat von Zacherl (Beitrag 1359746)
Im Falle von String bewirkt es vor allem, dass die automatische Referenzzählung deaktiviert und grade KEINE lokale Kopie erzeugt wird.

Wird die Referenzzählung wirklich deaktiviert oder wird es der ARC nur mitgeteilt, dass die String-Variable noch woanders benötigt wird?

Ist eigentlich nur ein Interna. Konkret wird bei konstanten Strings der RefCount immer auf -1 gesetzt, um zu verhindern, dass der Speicher für den String ungewollt freigegeben wird. Die Online-Hilfe dokumentiert dieses Verhalten hier:
Zitat:

For string literals, the compiler generates a memory block with the same layout as a dynamically allocated string, but with a reference count of -1. String constants are treated the same way, the only difference from literals being that they are a pointer to a -1 reference counter block.
When a pointer to a string structure (source) is assigned to a string variable (destination), the reference counter dictates how this is done. Usually, the reference count is decreased for the destination and increased for the source, as both pointers, source and destination, will point to the same memory block after the assignment.
If the source reference count is -1 (string constant), a new structure is created with a reference count of 1. If the destination is not nil, the reference counter is decreased. If it reaches 0, the structure is deallocated from the memory. If the destination is nil, no additional actions are taken for it. The destination will then point to the new structure.
Im Grunde geht es bei
Delphi-Quellcode:
const S: String
weniger um den RefCounter ansich, sondern unnötige lokale Kopien und Speicheroperationen. Wenn man sich mal anschaut, wie eine leere Funktion mit String Parameter ohne
Delphi-Quellcode:
const
oder
Delphi-Quellcode:
var
aussieht:
Code:
00822538 55               push ebp
00822539 8BEC            mov ebp,esp
0082253B 51               push ecx
0082253C 8945FC          mov [ebp-$04],eax
0082253F 8B45FC          mov eax,[ebp-$04]
00822542 E82D79BEFF      call @UStrAddRef
00822547 33C0             xor eax,eax
00822549 55               push ebp
0082254A 686B258200       push $0082256b
0082254F 64FF30           push dword ptr fs:[eax]
00822552 648920           mov fs:[eax],esp
00822555 33C0             xor eax,eax
00822557 5A              pop edx
00822558 59               pop ecx
00822559 59               pop ecx
0082255A 648910           mov fs:[eax],edx
0082255D 6872258200       push $00822572
00822562 8D45FC          lea eax,[ebp-$04]
00822565 E82678BEFF      call @UStrClr
0082256A C3               ret
0082256B E9C86DBEFF      jmp @HandleFinally
00822570 EBF0             jmp $00822562
00822572 59               pop ecx
00822573 5D              pop ebp
00822574 C3               ret
Im Vergleich zu einer Funktion mit
Delphi-Quellcode:
const
bzw.
Delphi-Quellcode:
var
String-Parameter:
Code:
00822578 55               push ebp
00822579 8BEC            mov ebp,esp
0082257B 51               push ecx
0082257C 8945FC          mov [ebp-$04],eax
0082257F 59               pop ecx
00822580 5D              pop ebp
00822581 C3               ret
Hier sieht man ganz gut, dass man durch diese kleine Optimierung doch deutlich Overhead einspart.

Benedikt Magnus 24. Jan 2017 07:20

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Dass der Compiler so etwas nicht automatisch optimiert war mir immer schon ein Rätsel...

uups 24. Jan 2017 08:57

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Zuerst möchte ich mich für die vielen hilfreichen Beiträge bedanken, habe wieder etwas dazu gelernt. Bevor ich nun die vielen nützlichen Vorschläge umsätze, möchte ich gerne zu meiner eigentlichen Frage zurückkehren: wäre mein geposteter Code so in Ordnung oder sollen die Zugriffe auf den Queue-Thread zusätzlich mit einer TCriticalSection abgesichert werden?

taveuni 24. Jan 2017 10:09

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Wenn ein anderer Thread TapDataReceiverQueue.AddToQueue aufruft musst Du natürlich im Thread
das:
Delphi-Quellcode:
        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;
absichern. Allerdings ACHTUNG bei Deinem eigenartigen Konstrukt. Da DeleteFromQueue() eine Prozedur von TapDataReceiverQueue ist UND Du dort schon die CriticalSection benutzt darfst Du diese nicht noch mal absichern. Ansonsten kann ein DEADLOCK entstehen. Also besser alle Methoden in TapDataReceiverQueue integrieren und mit Criticalsection absichern. Dann nie direkt auf die Liste gehen. So kannn nichts passieren.

Ergänzung: Das TapDataReceiverQueue.Count natürlich auch.

uups 24. Jan 2017 16:33

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

Zitat von taveuni (Beitrag 1359808)
Allerdings ACHTUNG bei Deinem eigenartigen Konstrukt. Da DeleteFromQueue() eine Prozedur von TapDataReceiverQueue ist UND Du dort schon die CriticalSection benutzt darfst Du diese nicht noch mal absichern. Ansonsten kann ein DEADLOCK entstehen. Also besser alle Methoden in TapDataReceiverQueue integrieren und mit Criticalsection absichern. Dann nie direkt auf die Liste gehen. So kannn nichts passieren.

Das habe ich jetzt nicht ganz verstanden.
Delphi-Quellcode:
DeleteFromQueue()
wird nur aus dem vom Thread selbst aufgerufen. Public sind nur
Delphi-Quellcode:
AddToQueue()
und
Delphi-Quellcode:
Count()
. In beiden sind die Zugriffe auf die TObjectList geschützt (für Count nachträglich). Andere Methoden gibt es nicht. Was ist gemeint mit "alle Methoden in TapDataReceiverQueue integrieren und mit Criticalsection absichern"?

taveuni 24. Jan 2017 16:46

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Delphi-Quellcode:
        try
          if Assigned(FQueue.Items[0]) then // --> hier ist ein direkter Zugriff auf die "interne" Liste ohne Absicherung
          try
            QueueItem := TapDataReceiverQueueItem(FQueue.Items[0]); // hier ebenfalls
            TapDataReceiver.Create(false, QueueItem.ClientData, QueueItem.Target, QueueItem.Priority); // was ist eigentlich das hier?
          finally
            DeleteFromQueue(0); // hier ist der Zugriff über eine Methode der Klasse mit Absicherung
          end;
Das bedeutet Du mischst Zugriffe auf die Lokale Liste der Klasse (ohne Absicherung) mit Methoden der Klasse (inkl. Absicherung).
Meiner Meinung nach solltest Du die Klasse
Delphi-Quellcode:
TapDataReceiverQueue
so bauen dass Du aus dem Thread alles mit Klassenmethoden machen kannst (besser MUSST). Zum Beispiel eine Methode Pop um die Objekte zu holen. In der Klasse sicherst Du alles mit der CriticalSection ab. Somit muss sich kein Aufrufer der Methoden um etwas kümmern. Egal aus welchem Thread. Dafür würde sich übrigens die Generische TQueue ev. besser eignen als die TObjectlist.

HolgerX 24. Jan 2017 17:36

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

Also:

Generell MUSS jeder Zugriff von verschiedenen Threads auf Variablen gekapselt werden.

Dabei ist es egal ob auf die Variable von externen Threads oder vom eigenen Thread darauf zugegriffen wird.
Es sind alle konkurierende Threads.

Somit müssen 'Add' 'Count' 'Delete' 'Extract' 'READ' und alle eventuell weiteren Zugriffe auf deine Liste gekapselt werden!

a.def 24. Jan 2017 18:45

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
Heißt kapseln, dass CriticalSection Enter und Leave bzw TThread.Queue() reicht?

Rollo62 24. Jan 2017 19:32

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
http://docwiki.embarcadero.com/Libra...nterlocked.Add

Oder die ganzen TInterlocked .... Methoden für einfache Variablenzugriffe.



Rollo

samso 25. Jan 2017 07:32

AW: Zugriffe auf Objekt aus mehreren Threads - wie richrig synchronisieren?
 
In Post #5 wurde vorgeschlagen statt TObjectList eine TThreadlist zu verwenden. Dieser Vorschlag ist auch deshalb sehr klug, weil Du dann mehr oder weniger gezwungen wirst alles richtig zu machen. TThreadlist zwingt Dich den Zugriff auf die Liste abzusichern.

Deine Konstruktion
Delphi-Quellcode:
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;
ist unzulässig, weil Du dabei davon ausgehst, dass sich FQueue.Items während Deines Zugriffs nicht verändert. Das ist aber keine zugesicherte Eigenschaft. Ein anderer Thread kann, während Du FQueue.Items benutzt, eine neue Aufgabe zur Queue hinzufügen. Dadurch könnte sich potentiell die Adresse von FQueue.Items ändern. Um das zu verhindern musst Du also vor dem Zugriff auf FQueue die Queue locken.
Wenn Du TThreadlist benutzt, würde der Code etwa so aussehen:
Delphi-Quellcode:
var
  LList: TList;
begin
...
  QueueItem := nil;
  try
    LList := FQueue.LockList;
    try
      // Pop...
      if LList.Count>0 then
      begin
        QueueItem := TapDataReceiverQueueItem(LList.Items[0]);
        LList.Delete(0);
      end;
    finally
      FQueue.UnlockList;
    end;
    if Assigned(QueueItem) then
      TapDataReceiver.Create(false, QueueItem.ClientData, QueueItem.Target, QueueItem.Priority);
  finally
    QueueItem.Free;
  end;
Dabei ist zu beachten, dass man niemals auf LList außerhalb des Schutzblockes zugreift. Also nicht so
Delphi-Quellcode:
  LList := FQueue.LockList;
  FQueue.Unlocklist;
  if LList.Count>0 then...
Dadurch wird die Sperrung der Liste ausgehebelt und das Ganze verhält sich wieder wie TObjectList.


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:36 Uhr.

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