Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Ist SetLength Thread-safe? (https://www.delphipraxis.net/142248-ist-setlength-thread-safe.html)

iphi 24. Okt 2009 14:56


Ist SetLength Thread-safe?
 
Hallo,

ich habe einen Thread, der Daten sammelt und diese in ein globales dynamisches Array ablegen soll.
Ist es ok, wenn der Thread das array mit setlength vergrößert oder kann es darurch z.B. mit dem VCL Hauptthread zu Speicherkonflikten kommen?

Gruß, Thomas

Apollonius 24. Okt 2009 15:14

Re: Ist SetLength Thread-safe?
 
Da stellt sich mal wieder die Frage: Was bedeutet eigentlich threadsicher?
Die von dir gewünschte Antwort dürfte allerdings nein lauten. Es kann dir nämlich passieren, dass der Hauptthread einen Zeiger dereferenziert, den der zweite Thread schon dem Speichermanager als frei gemeldet hat.

himitsu 24. Okt 2009 16:25

Re: Ist SetLength Thread-safe?
 
Also ganz einfach alle Zugriffe auf dieses Array über eine CriticalSection oder Ähnliches absichern.

Ganz Einfach, alles, was mit Pointer arbeitet und sich nicht als Delphi-Referenz durchsuchenthreadvar deklarieren läßt, das mußt du absichern.

Von Natur aus sind erstmal nur die Referenzzählungen von Strings (AnsiString/UnicodeString) und Interfaces ThreadSave, weil Delphi dort selber schon threadsichere Zugriffe nutzt ... um alles andere mußt du dich selber kümmern.

iphi 25. Okt 2009 07:50

Re: Ist SetLength Thread-safe?
 
Danke für das Feedback.

Zitat:

Es kann dir nämlich passieren, dass der Hauptthread einen Zeiger dereferenziert, den der zweite Thread schon dem Speichermanager als frei gemeldet hat.
Jetzt frage ich mich allerdings, wie ichs richtig mache.

Ich möchte folgendes erreichen:
Der VCL-Hauptthread übergibt dem Sammlerthread z.B. in SammlerThread.Create ein leeres dynamisches Array, genauer einen Zeiger auf ein globales solches. Der Sammlerthread sammelt Daten, vergrößert das globale Array nach Bedarf und schreibt die gesammelten Daten blockweise hinein.
Der VCL Hauptthread sieht an der Länge des dynamischen Arrays immer, wie viele Daten schon gesammelt sind.
Derselbe Sammlerthreadcode soll in verschiedenen Instanzen in verschiedene Arrays sammeln, daher ist die Übergabe des Arraypointers erforderlich.

Ist das so machbar und wenn ja, mit welcher Konstruktion?

Gruß, Thomas

wicht 25. Okt 2009 09:03

Re: Ist SetLength Thread-safe?
 
Guten Morgen!
Hier mal ein kleines Beispiel, so zusammengetippt und ohne Anspruch auf irgendetwas, aber es sollte die Benutzung von CriticalSections zeigen, ausserdem gibt es noch ein Event, um der Form bescheid zu sagen. Hoffe, hier sind nicht allzuviele Böcke drin :-D ..

Delphi-Quellcode:
interface

type
  TMyArray = array of string;

  TDataThread = class(TThread)
  private
    FOnNotify: TNotifyEvent;
    FArray: TMyArray;

    procedure DoNotify;
  protected
    procedure Execute; override;
  public
    constructor Create(Arr: TArray);
    property OnNotify: TNotifyEvent read FOnNotify write FOnNotify;
  end;

  TMyForm = class(TForm)
  private
    ThreadA: TDataThread;
    ThreadB: TDataThread;
    procedure ThreadNotify(Sender: TObject);
  public
    constructor Create(AOwner: TComponent);
  end;

var
  CS: TCricicalSection;

implementation

constructor TDataThread.Create(Arr: TMyArray);
begin
  inherited Create(True);
  FArray := Arr;
end;

procedure TDataThread.Execute;
var
  i: Integer;
  FTempArray: TArray;
begin
  SetLength(FTempArray, 0);

  while True do
  begin
    // Mache ganz viel. Befülle das FTempArray.
    // Wenn FTempArray "voll" ist, wird
    // die CS betreten, das echte Array manipuliert,
    // Der Form bescheid gesagt, und dann geht es weiter.

    // Einen gesammelten Datensatz dem internen Array hinzufügen
    SetLength(FTempArray, Length(FTempArray) + 1);
    FTempArray[High(FTempArray)] := 'asdf';

    if Length(FTempArray > 100)
    begin
      CS.Enter; // Zugriff global absichern
      SetLength(FArray, Length(FArray) + Length(FTempArray));
      for i := 0 to Length(FTempArray) do // kA ob Schleife so passt, ist noch zu früh.
        FArray[Length(FArray) - Length(FTempArray) + i] := FTempArray[i];
      CS.Leave; // Und Zugriff wieder erlauben
      Synchronize(DoNotify); // Und der Form bescheid sagen, dass neue Daten da sind
    end;
    SetLength(FTempArray, 0);
  end;
end;

procedure TDataThread.DoNotify;
begin
  if Assigned(FOnNotify)
    FOnNotify(Self);
end;

constructor TMyForm.Create(AOwner: TComponent);
begin
  inherited;
  SetLength(FArray, 0);
  CS := TCriticalSection.Create;
  ThreadA := TDataThread.Create(FArray);
  ThreadA.OnNotify := ThreadNotify;
  ThreadA.Resume;

  ThreadB := TDataThread.Create(FArray);
  ThreadB.OnNotify := ThreadNotify;
  ThreadB.Resume;
end;

procedure TMyForm.ThreadNotify(Sender: TObject);
begin
  // Thread hat Datenblock gesammelt, hier könnte
  // man jetzt auf das Array zugreifen, ganz bequem.
  // Wichtig ist hier auch die CriticalSection und
  // man sollte zusehen, dass das, was man hier macht,
  // ganz schnell geht, weil ein Thread, der sein
  // internes Array voll hat und die Daten übertragen
  // möchte das bis zum CS.Leave nicht kann.
  CS.Enter;
  ShowMessage(FArray[0]);
  CS.Leave;
end;

himitsu 25. Okt 2009 10:46

Re: Ist SetLength Thread-safe?
 
Zitat:

Zitat von iphi
daher ist die Übergabe des Arraypointers erforderlich.

am Besten wäre es, wenn du das Array in einer Klasse verwaltest und Zugriffe immer nur über Funktionen/Properties dieser Klasse (das kann auch der Hauptthread, bzw. das Formular sein) wird auf die Daten des Arrays zugegriffen.
Dann hast du auch die Synchronisierung (CriticalSection) zentral an einer Stelle.

Ansonsten mußt du ja die CriticalSection und einen Zeiger auf die Array-Variable übergeben und nicht auf das Array selber.
Dann kann sich jeder selber den Weg zum Array dereferenzieren und kann dieses auch bearbeiten.

Oder man könnte noch über Synchronize auf das Array im Haupthtread/Formular zugreifen.

[edit]
@wicht: TCricicalSection kommt natürlich auch dahin, wo das Array liegt :zwinker:
Stell dir mal vor man hat von den Klassen mehere, dann bremsen ihre Zugriffe sich gegenseitig aus.
Und wenn jemand TMyForm mehrfach erstellt, dann drehen die Sections durch, weil sie überschrieben und auch mal vorzeitig freigegeben werden (also die CS, welche als letztes in der Variable stand wird weg sein, wenn man ein solches Formular wieder freigibt ... das ist auch der Grund, warum globale Variablen meißtens böse sind :zwinker: ).

wicht 25. Okt 2009 12:19

Re: Ist SetLength Thread-safe?
 
Ist ganz klar. War nur als kleines Beispiel gemeint, aber man sollte es schon noch überarbeiten. Die Verwendung einer CS und vom Synchronize wird jedenfalls demonstriert, mehr wollte ich nicht.
So früh am Morgen Premiumcode schreiben, dafür bin ich schon zu alt.. :lol:

sirius 25. Okt 2009 12:44

Re: Ist SetLength Thread-safe?
 
Hier mal als Beispiel für die von himi angesprochene Klasse (für einen string mit speziellen Aufgaben)
Delphi-Quellcode:
TSyncString=class(TSimpleRWSync)
  private
    FValue:String;
    procedure setValue(Value:string);
    function getValue:string;
  public
    function length:Integer;
    property Value:string read getValue write SetValue;
    procedure Add(const Value:string); //string um einen weiteren ergänzen
    procedure Delete(Pos,Count:Integer); //ein teil des strings löschen
  end;


{ TSyncString }

procedure TSyncString.Add(const Value: string);
begin
  BeginWrite;
  try
    FValue:=FValue+Value;
  finally
    EndWrite;
  end;
end;

procedure TSyncString.Delete(Pos, Count: Integer);
begin
  BeginWrite;
  try
    system.Delete(FValue,Pos,Count);
  finally
    EndWrite;
  end;
end;

function TSyncString.getValue: string;
begin
  BeginRead;
  try
    result:=FValue;
  finally
    EndRead;
  end;
end;

function TSyncString.length: Integer;
begin
  BeginRead;
  try
    result:=system.length(FValue);
  finally
    EndRead;
  end;
end;

procedure TSyncString.setValue(Value: string);
begin
  BeginWrite;
  try
    FValue:=Value;
  finally
    EndWrite;
  end;
end;
TSimpleRWSync hat so eine CS. Wenn man noch mehrere Threads zugreifen lässt, dann ist es sinnvoll lesende von schreibenden Zugriffen zu "trennen", das macht dann TMultiSync..irgendwas

Auch ist es sinnvoll sich TThreadlist anzusehen.

iphi 25. Okt 2009 13:21

Re: Ist SetLength Thread-safe?
 
Erstmal vielen Dank für Euer umfangreiches Feedback.
Für mich als Threadinganfänger ist das noch ein bisschen schwierig nachzuvollziehen.

Hier erst mal ein paar Fragen für mein Verständnis:

1. Nach meinem Verständnis sind nur Schreibzugriffe "böse", die sich potentiell mit anderen Schreib- oder Lesezugriffen überschneiden können. Wenn nur gelesen wird, sind nach meinem Verständnis critical sections unnötig. Richtig?

2. Nach meinem Verständnis sind critical sections auch bei Schreibzugriffen unnötig, wenn andersweitig sichergestellt ist, dass kein anderer Code auf dieselbe Resource gleichzeitig zugreift. Richtig?

3. Kann es sein, dass es die Klassen TSimpleRWSync bzw. TMultiSync in Delphi6 (Personal) nicht gibt?

Wenn meine Denke soweit stimmt, dann hätte ich mir folgende Strategie zurechtgelegt:

Ich hab ein globales dynamisches Array x von dynamischen Arrays. Wenn neue Daten anliegen, vergrößert der Sammlerthread x um ein Arrayelement, welches seinerseits ja ein dynamisches Array ist, setzt letzteres auf die notwendige Länge und schreibt die neuen Daten da rein. Über einen globalen "Füllstandsanzeiger" könnte man gewährleisten, dass niemand lesend auf das noch nicht aktualisierte Element (=Subarray) von x zugreift, so lange der Sammlerthread noch nicht fertig ist mit schreiben. Auf alle anderen Elemente (=Subarrays) von x müsste man problemlos zugreifen können, da die nicht mehr verändert werden.
Dann müsste nur der Zugriff auf den Füllstandszeiger in eine critical section (und setlength-Anweisungen natürlich).

Macht das Sinn?

Gruß,
Thomas

Apollonius 25. Okt 2009 13:45

Re: Ist SetLength Thread-safe?
 
Du musst dir bewusst machen, was bei SetLength alles geschehen kann.
Nehmen wir an, du hast ein Array mit 100 Elementen. Es liegt als Datenstruktur im Heap. Wenn du es nun auf tausend Elemente vergrößern willst, wird, wenn du Glück hast, noch Speicher nach dem alten Array frei sein, der dann dem Array hinzugefügt wird. Wenn kein Speicher frei ist, muss das Array verschoben werden. Und genau dann ist deine Annahme verletzt.
Zitat:

Auf alle anderen Elemente von x müsste man problemlos zugreifen können, da die nicht mehr verändert werden.
Wenn du Pech hast, holt sich der Lese-Thread nämlich den alten Arrayzeiger, und bevor er ihn dereferenzieren kann, wird das Array von einem schreibenden Thread verschoben. Wenn du Glück hast, raucht dir der Lese-Thread mit einer Zugriffsverletzung ab - andernfalls kannst du dir leicht den Heap zerschießen.

Tryer 25. Okt 2009 13:48

Re: Ist SetLength Thread-safe?
 
Wenn die Größe eines Arrays geändert wird kann es sein das das gesamte Array im Speicher verschoben wird. Auch hierbei muss also synchronisiert werden, um heimtückische (weil selten auftretende) Fehler zu vermeiden. Man hängt vermeindlich nur hinten etwas an, aber auch das erste Element wird im Speicher verschoben.

Reine Lesezugriffe müssen natürlich nicht synchronisiert werden.
Neben den CritialSections gibt es noch andere Möglichkeiten der Synchronisation (Mutex, Semaphore, Events, Messages..) wo der eine Thread dem anderen klar sagt "ich bin fertig und warte jetzt bis du fertig bist".

himitsu 25. Okt 2009 13:55

Re: Ist SetLength Thread-safe?
 
genau sowas verhindern dieses TMultiSync-Dinger

sie erlauben gleichzeitige Lesezugriffe, aber nur wenn in diesem Moment nicht schreibend zugegriffen wird
oder sie erlauben nur einen Schreibzugriff und sonst nix.

somit ist sichergestellt, daß immer nur einer ändert
und man nicht lesen kann, wärend geändert wird.

iphi 25. Okt 2009 14:27

Re: Ist SetLength Thread-safe?
 
Ok, so langsam kapiere ich wie das Memorymanagement funktioniert. Ich habe mich schon immer gefragt, wie es funktioniert, ein array dynamisch zu vergrößern, wenn am Ende des arrays kein Platz mehr ist...

Diese Umkopiererei will ich eigentlich vermeiden.

Wenn mein Pointerarray statisch ist (=vordefinierte Länge), dann müsste es doch fix im Speicher liegen, oder? Die Pointer(=dynamische Arrays) zeigen dann auf nil oder auf dynamische Arrays fixer Länge.
Bsp:
Delphi-Quellcode:
type
  Tx: array of char;
x: array[0..100] of Tx;
Wenn ich z.B. setlength(x[50],100) mache, würde das die restlichen Arrays x[i] (sofern allokiert) im Speicher fix lassen oder würden die ggf. auch umkopiert werden?

P.S. Hab in Delphi6 TMultiReadExclusiveWriteSynchronizer gefunden. Der scheint ein geeignetes Werkzeug für meine Anwendung, oder?

Gruß Thomas

Apollonius 25. Okt 2009 14:31

Re: Ist SetLength Thread-safe?
 
Nur x[50] kann umkopiert werden. Der Speichermanager weiß ja gar nicht, wo sich Referenzen zu den anderen Speicherblöcken befinden.

iphi 25. Okt 2009 15:09

Re: Ist SetLength Thread-safe?
 
Ok, das ginge also. Das mit dem statischen Array ist allerdings unschön, weil ich a priori gar nicht weiß, wie viele Datenblöcke kommen werden und es halt unschön ist ein überlanges Array von Pointern vorzuhalten.

Was gibt es denn noch für Möglichkeiten, dynamisch wachsende sequentiell schreib- und lesbare Datenstrukturen zu realisieren? Ich könnte mir so etwas wie eine verkettete Liste, d.h. jeder Datenblock enthält einen Pointer auf den nächsten Datenblock vorstellen. Wie könnte ich solche Strukturen in Delphi dynamisch erzeugen/vergrößern?

Apollonius 25. Okt 2009 15:32

Re: Ist SetLength Thread-safe?
 
Verkettete Listen sind für Aufgaben mit mehreren Threads richtig genial. Man kann dort nämlich völlig ohne Locks auskommen. Windows stellt zum Beispiel Funktionen für einen Stack zur Verfügung, der mit beliebig vielen zugreifenden Threads immer konsistent bleibt. Queues lassen sich auch auf diese Weise implementieren, das ist allerdings ein bisschen komplizierter. Ich habe das auch mal gemacht, der Code ist allerdings ungetestet und komplett in Assembler. Falls es dich interessiert kann ich die Unit hochladen.

Was spricht jetzt eigentlich gegen Arrays? Gut, du brauchst immer irgendein Lock, aber in den meisten Fällen ist das vernachlässigbar.

himitsu 25. Okt 2009 15:44

Re: Ist SetLength Thread-safe?
 
Auch verkettete Listen müssen abgesichert werden!
Aber dort kann man es so einrichten, daß man jedes Element einzeln absichert und nicht die ganze Liste auf einmal ... so lassen sich viele Zugriffe an verschiedenen Stellen womöglich besser optimieren, da sie sich so nicht gegenseitig stören.

Apollonius 25. Okt 2009 15:46

Re: Ist SetLength Thread-safe?
 
Du kannst verkettete Listen so aufbauen, dass du keine Locks, sondern nur Compare-and-Swap brauchst.

himitsu 25. Okt 2009 15:54

Re: Ist SetLength Thread-safe?
 
Zitat:

Zitat von Apollonius
Du kannst verkettete Listen so aufbauen, dass du keine Locks, sondern nur Compare-and-Swap brauchst.

Compare-and-Swap/InterlockedExchange zähle ich mit zum Absichern.

drum hab ich auch nicht looken/sperren sondern "absichern" gesagt :zwinker:


Alle Zeitangaben in WEZ +1. Es ist jetzt 22:22 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-2025 by Thomas Breitkreuz