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 |
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. |
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 threadvar 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. |
Re: Ist SetLength Thread-safe?
Danke für das Feedback.
Zitat:
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 |
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; |
Re: Ist SetLength Thread-safe?
Zitat:
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: ). |
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: |
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:
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
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; Auch ist es sinnvoll sich TThreadlist anzusehen. |
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 |
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:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:50 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