Änderungen von Variablen synchronisieren
Hallo,
ich hoffe, der Titel ist treffend. Ich habe eine Variable vom Typ String, die sowohl innerhalb eines Threads als auch außerhalb des Threads verändert werden kann. Die Wahrscheinlichkeit ist wohl sehr gering, aber ich befürchte, dass es theoretisch zu Problemen kommen könnte, wenn ich versuche, den Wert dieser Variablen gleichzeitig innerhalb des Threads und außerhalb des Threads zu ändern. Hier mein Lösungsversuch:
Delphi-Quellcode:
Solange der Wert innerhalb des Threads durch die Variable ThreadString_Locked gesperrt ist, kann der Variablen außerhalb des Threads nichts zugewiesen werden.
Var ThreadString:AnsiString;
TreadString_Locked:Boolean; //Innerhalb des Threads Begin ... ThreadString_Locked:=True; ThreadString:='Neuer Wert'; ThreadString_Locked:=False; ... End; //außerhalb des Threads: Function SetThreadString(s:AnsiString):Boolean; Var TimeOut:Integer; Begin TimeOut:=0; Repeat IF not(ThreadString_Locked) then BEGIN ThreadString:=s; Result:=True; END ELSE Result:=False; Inc(TimeOut); Until TimeOut>1000; End; Ist dieser Ansatz sinnvoll bzw., gibt es eine einfachere oder besser Lösung? Gruß |
AW: Änderungen von Variablen synchronisieren
|
AW: Änderungen von Variablen synchronisieren
Apropos Timeout -> du hast da nen Vergleich mit 1000 gemacht, was evt. impliziert, dass du wirklich 1000 ms (1 sek) warten möchtest. Das ist aber nicht der Fall.. Das sind einfach 1000 Schleifendurchläufe; daher ist das auch abhängig von der Prozessorgeschwindigkeit. Verarbeitet dein Prozessor einen Schleifendurchgang in genau einer ms, dann würde das stimmen, nur ist das nicht der Fall. Diese Schleife ist binnen kürzester Zeit durchlaufen (<10ms?)
...nur ne Kleinigkeit ^^ |
AW: Änderungen von Variablen synchronisieren
Booleans sind nicht threadsicher, vorallem da sie nichtmal atomar änderbar sind, da die CPU mir kompletten Registern arbeitet und dann nur die Bytes raus ANDed und SCHIFTet. :stupid:
Die CPUs kennen da einen speziellen Befehl LOCK z.B.
Delphi-Quellcode:
statt
LOCK MOV [EAX], EDX
Delphi-Quellcode:
.
MOV [EAX], EDX
Und die WinAPI kennt die Interlocked-Behle, wie InterlockedExchange. Deine "innere" Änderung von ThreadString ist nicht abgesichert, denn da wird nicht geprüft, ob der String gerade von SetThreadString verändert wird. Gut, man könnte jetzt beide Zeiten absichern und dann äußere und inner Änderungen nur über dieses SetThreadString ausführen,
Delphi-Quellcode:
aber hier kann es immernoch vorkommen, daß mehrere Threads gleichzeitig durch das IF kommen und :=True setzen, da diese Beiden erst Recht nicht atomar sind.
Function SetThreadString(s:AnsiString):Boolean;
Var TimeOut:Integer; Begin TimeOut:=0; Repeat IF not(ThreadString_Locked) then BEGIN ThreadString_Locked:=True; ThreadString:=s; ThreadString_Locked:=False; Result:=True; END ELSE Result:=False; Inc(TimeOut); Until TimeOut>1000; End; Ich hatte letzens die CriticalSection über TryEnter und eine Schleife mit einem TimeOut versehn. Schön ist das nicht unbedingt, aber es funktioniert wenigstens. |
AW: Änderungen von Variablen synchronisieren
Vielen Dank für Eure Antworten!
Mir brummt schon ganz schön der Schädel. Vielleicht warte ich doch lieber, bis der Thread beendet wurde ;-) Brauche ich zum Lesen auch eine CriticalSection? Wird der Code innerhalb der CriticalSection in eine Warteschleife gelegt und später abgearbeitet oder einfach nicht ausgeführt/ignoriert? Letzteres wäre vorallem beim Lesen ungünstig. Zitat:
Bin gerade über TMultiReadExclusiveWriteSynchronizer gestolpert, was haltet ihr davon? Ich muss den Wert vorwiegend lesen. |
AW: Änderungen von Variablen synchronisieren
Eigentlich ist das mit der TCriticalSection sehr einfach.
Stell dir eine Kreuzung vor. Die beiden Straßen sind einmal der MainThread und der Thread den du laufen lässt. Die CriticalSection ist nun die Ampel, die dafür sorgt, dass über den Kreuzungspunkt (Zugriff auf die Variable) immer nur eine Fahrbahn (Thread) freigegeben ist.
Delphi-Quellcode:
Bei
TMyThread = class( TThread )
strict private FCS : TCriticalSection; private FIntStr : string; procedure SetIntStr( const Value : string ); function GetIntStr : string; protected procedure Execute; override; public property IntStr : string read GetIntStr write SetIntStr; end; ... procedure SetIntStr( const Value : string ); begin FCS.Enter; // Betreten der CS try FIntStr := Value; finally FCS.Leave; // Verlassen der CS end; end; function GetIntStr : string; begin FCS.Enter; // Betreten der CS try Result := FIntStr; finally FCS.Leave; // Verlassen der CS end; end;
Delphi-Quellcode:
wird solange gewartet, bis die CriticalSection frei ist und dann betreten.
FCS.Enter
Innerhalb eines Thread-Kontexts darf die CS sooft betreten werden, wie man möchte. Die CS sperrt ja nur gegen Zugriffe durch unterschiedliche Thread-Kontexte. Die Getter und Setter der Eigenschaft kümmern sich nun darum, dass der Zugriff geregelt wird, egal aus welchem Thread-Kontext man darauf zugreift. Aber ... innerhalb des Threads könnte man ja direkt auf
Delphi-Quellcode:
zugreifen, aber dieser Zugriff ist nicht abgesicht (über die CS), also sollte der Zugriff auch innerhalb des Threads über die Eigenschaft und nicht über das Feld erfolgen.
FIntStr
Delphi-Quellcode:
Der Vollständigkeit halber sei noch erwähnt, dass die Instanz von FCS natürlich von TMyThread im Constructor erzeugt und im Destructor wieder freigegeben werden sollte bzw. muss.
procedure TMyThread.Execute;
begin while not Terminated do begin ... // FIntStr := 'So nicht'; IntStr := 'So geht das'; ... end; end; end; |
AW: Änderungen von Variablen synchronisieren
Danke Sir Rufo :-D
Habs gerade selbst getestet und gesehen, dass alles schön sauber nacheinander abgearbeitet wird. Ich benutze in meinem Programm eine TList, dessen Items ich jeweils einen Record zuweise. Ein Bestandteil dieses Records ist die String-Variable (in meinem Beispiel ThreadStrg). Jedes Bearbeiten der Liste (Add, Insert, Delete, Exchange, Clear) im Hauptthread und natürlich das Ändern sowie das Lesen des Strings im Thread und im Hauptthread muss ich also einfach in die CriticalSection packen. Zitat:
Für meinen Fall ist es dann wahrscheinlich am sinnvollsten, die Instanz FCS zusammen mit TList zu erstellen und wieder freizugeben? @Aphton: Danke für Deinen Hinweis. Das ist mir schon klar. Für ein zeitgebundenes Timeout würde ich etwas anders an die Sache rangehen. |
AW: Änderungen von Variablen synchronisieren
Microsoft hat leider die Verwaltung der CS umgestllt.
In den Debuginfos der CS gibt es eigentlich ein Event-Objekt, an welches ich mich dranhängen wollte, um nicht pollen zu müssen, aber leider ist dort, zumindestens in Win7/Server2008/Server2011 immer nur "Nichts" (0) eingetragen. :cry: In der Unit SyncObjs gibt es auch Objekte, welche ein TimeOut verwenden können. PS: Deine 1000-Schleifendurchläufe dürften schneller sein, als die Speicherverwaltung eines Strings, was sich etwas ungünstig auswirken dürfte. |
AW: Änderungen von Variablen synchronisieren
Danke himitsu!
Das klingt nicht so gut :( Hier mal ein Versuch, das ganze mit InterlockedExchange sicher zu machen:
Delphi-Quellcode:
Das weitere Verarbeiten der Liste (Sortieren, etc.) findet nur im Mainthread statt. Hierbei müsste ich einfach überprüfen, ob der Thread aktiv ist oder ThreadString_Locked auf True gesetzt ist.
Var ThreadString:AnsiString;
ThreadString_Locked:Integer; ... ThreadString_Locked:=Integer(False); ... Function SetThreadString(S:AnsiString):Boolean; Var TimeOut:Integer; Begin TimeOut:=0; Repeat IF InterlockedExchange(ThreadString_Locked,Integer(True)) = 0 then BEGIN ThreadString:=S; InterlockedExchange(ThreadString_Locked,Integer(False)); Result:=True; END ELSE Result:=False; Inc(TimeOut); Until (TimeOut>1000) OR Result; End; Mal noch eine generelle Frage: Solange ich den Wert einer globalen Variablen nur innerhalb "eines" Threads ändere, kann doch eigentlich nicht passieren, und beim Lesen doch sowieso nichts? |
AW: Änderungen von Variablen synchronisieren
Zitat:
Ergo Lesen und Schreiben absichern. |
AW: Änderungen von Variablen synchronisieren
Zitat:
|
AW: Änderungen von Variablen synchronisieren
So, vielen Dank nochmal an alle. Ich habe nun alle wichtigen Operationen der TList (Add, Insert, Delete, Exchange, Clear) und natürlich das Umbenennen des Strings mit Hilfe von TCriticalSection in kritische Abschnitte gepackt. Die Performance ist nach wie vor gut.
Zitat:
Delphi-Quellcode:
While not(FCS.TryEnter) do InfiniteLoop;
... tue irgendetwas FSC.Leave |
AW: Änderungen von Variablen synchronisieren
Die Mühe bei der TList hättest du dir sparen können, wenn du einfach TThreadList benutzen würdest :)
BTW: Du solltest immer mit einem Schutzblock arbeiten:
Delphi-Quellcode:
und auch das ist eigentlich völlig überflüssig, weil du hiermit genau das gleiche erreichst wie mit
While not(FCS.TryEnter) do InfiniteLoop;
try ... tue irgendetwas finally FSC.Leave; end;
Delphi-Quellcode:
FCS.Enter; // Wartet so lange, bis die CS betreten werden kann
try ... tue irgendetwas finally FCS.Leave; end; |
AW: Änderungen von Variablen synchronisieren
TThreadlist hatte ich auch bereits gefunden. Allerdings fehlt dort Insert und Exchange oder habe ich etwas übersehen? Gut, das könnte man im Notfall noch selbst implementieren.
Zitat:
EDIT: Das geht ja anscheinend doch. Muss ich mir nochmal gründlich unter die Lupe nehmen. Man lernt eben nie aus. Vielen Dank für den Hinweis. |
AW: Änderungen von Variablen synchronisieren
Pollen ist hier leider die einzige Möglichkeit, wenn man ein TimeOut benötigt, also bei der CS.
Ansonsten müßte man auf Events (z.B. SetEvent oder TEvent) umsteigen. Beim Pollen muß meventuell an aufpassen, daß man nicht die CPU hochtreibt. Ich bei da Pausen in die Schleife, so mit je um die 20 Millisekunden. Aber dort kann man sich unter umständen auch aussperren, wenn zwei/mehrere Threads in schneller Follge drauf zugreifen und zwischen 2 Zugriffen die Zeit kürzer ist, als die Pause. |
AW: Änderungen von Variablen synchronisieren
Zitat:
Und zwar habe ich mir eine einfache threadsichere Klasse (mit CriticalSections) gebaut, die sich um alles kümmert. Ich weiss, dafür gibts die ThreadList, die fand ich von der Schreibarbeit jedoch auch nicht wesentlich angenehmer. In meiner Klasse überlasse ich es dem Benutzer, ob er die Liste manuell sperren möchte oder ob dies automatisch geschieht.
Delphi-Quellcode:
Der Vorteil hierbei ist, dass man bei Bedarf einen größeren Code-Block sperren kann, ansonsten muss ich mich um nichts kümmern.
interface
Uses Classes, SysUtils, SyncObjs; type TMyList = class private MyList:TList; FCS : TCriticalSection; MustLock:Boolean; //Liste muss gesperrt werden Procedure Lock; //Internes Sperren Procedure Unlock; //Internes Freigeben public Procedure Locklist; //Manuell sperren Procedure Unlocklist; //Manuell freigeben Constructor Create; Destructor Destroy; Function Count:Integer; ... end; implementation Constructor TMyList.Create; Begin MyList:=TList.Create; FCS := TCriticalSection.Create; MustLock:=True; //Liste muss automatisch gesperrt werden End; Procedure TMyList.Lock; Begin IF MustLock then FCS.Enter; End; procedure TMyList.Unlock; begin IF MustLock then FCS.Release; end; Procedure TMyList.Locklist; Begin FCS.Enter; MustLock:=False; //Liste muss später NICHT automatisch gesperrt werden End; Procedure TMyList.Unlocklist; //EDIT: Reihenfolge der Codezeilen wurde getauscht Begin MustLock:=True; //Liste muss später automatisch gesperrt werden FCS.Release; End; Function TMyList.Count:Integer; Begin lock; try Result:=MyList.Count; finally unlock; end; End; Mal noch ne letzte Frage, wenn man in einer Schleife mehrere Items aus der Liste löscht, ist es dann besser, die komplette Schleife zu sprerren oder nur die einzelnen Delete? |
AW: Änderungen von Variablen synchronisieren
Zitat:
Delphi-Quellcode:
Procedure TMyList.Unlocklist; Begin MustLock:=True; //Liste muss später automatisch gesperrt werden FCS.Release; // erst jetzt darf ein anderer Thread an die Liste End; |
AW: Änderungen von Variablen synchronisieren
Zitat:
|
AW: Änderungen von Variablen synchronisieren
Erklär doch mal, wofür du das MustLock in LockList und UnlockList jeweils veränderst.
Das macht in meinen Augen keinen Sinn. |
AW: Änderungen von Variablen synchronisieren
Zitat:
Im Allgemeinen gilt, dass die CS sobald als möglich wieder freigegeben wird. |
AW: Änderungen von Variablen synchronisieren
Zitat:
Aus diesem Grund setze ich bei TMyList.Locklist MustLock:=False. Das bedeutet, dass ich z.B. innerhalb von Count keine CriticalSection mehr starten muss, da schon aktiv
Delphi-Quellcode:
Also wird der Code in Count ausgeführt.
IF MustLock then Lock
|
AW: Änderungen von Variablen synchronisieren
Ja, sowas hatte ich mir gedacht :)
Aber das ist überflüssig und verkompliziert die Sache unnötig. Eine CriticalSection sorgt dafür, das innerhalb selbiger nur ein ThreadKontext aktiv ist. Innerhalb dieses ThreadKontextes kann eine CS aber beliebig oft betreten werden. Die CS muss aber auch genauso oft wieder verlassen werden damit diese wieder frei für einen anderen ThreadKontext ist.
Delphi-Quellcode:
Diese Konstrukt funktioniert problemlos, denn wenn die CS betreten werden kann, dann funktioniert auch das zweite Betreten.
FCS.Enter;
FCS.Enter; TuWas; FCS.Leave; FCS.Leave; |
AW: Änderungen von Variablen synchronisieren
Alles klar :thumb: Danke, man lernt eben nie aus. Das Thema Threads wird mir noch die ein oder andere schlaflose Nacht bescheren (absolutes Neuland für mich) :roll:
|
AW: Änderungen von Variablen synchronisieren
Und das Try-Finally nicht vergessen, denn die CS muß eben unbedingt genau so oft verlassen/freigegeben werden, wie sie betreten/gesperrt wurde.
|
AW: Änderungen von Variablen synchronisieren
Zitat:
Delphi-Quellcode:
Function TMyList.Count:Integer;
Begin lock; try Result:=MyList.Count; finally unlock; end; End; |
AW: Änderungen von Variablen synchronisieren
Und das 'lock' bzw. 'unlock' sieht wie aus?
|
AW: Änderungen von Variablen synchronisieren
Zitat:
:stupid: :pale: :oops: |
AW: Änderungen von Variablen synchronisieren
Wozu das MustLock?
Erstmal das von Blup Genannte und wenn sich zwischen Lock und Unlock das MustLock ändert, dann stimmt die Anzahl auch nicht mehr. Im Prinzip ist das LockList/UnlockList umsonst, außerdem fehleranfällig und Lock/Unlock alleine reicht auch schon aus. |
AW: Änderungen von Variablen synchronisieren
Zitat:
Delphi-Quellcode:
Sir Rufo hat mich schon darauf hingewiesen, dass das Mustlock überflüssig ist.
MyList.Locklist;
Dateiinfo:=Leseausdatei(MyList.Item[5].FileName); //Leseausdatei ist kein Bestandteil der Klasse //Was passiert, wenn während der Leseausdatei Function gleichzeitig das MyList.Item[5] über MyList.Delete(5) gelöscht wird. Mylist.Item[5].Daten:=Dateiinfo; //Daten werden in falschen Item gespeichert. MyList.LockList; EDIT: Zitat:
|
AW: Änderungen von Variablen synchronisieren
Das geht ja dennoch.
Delphi-Quellcode:
Wenn du Mustlock entfernst, wirst du bemerken, daß Lock=LickList und Unlock=UnlockList.
MyList.Lock;
try Dateiinfo:=Leseausdatei(MyList.Item[5].FileName); //Leseausdatei ist kein Bestandteil der Klasse //Was passiert, wenn während der Leseausdatei Function gleichzeitig das MyList.Item[5] über MyList.Delete(5) gelöscht wird. Mylist.Item[5].Daten:=Dateiinfo; //Daten werden in falschen Item gespeichert. finally MyList.Unlock; end; |
AW: Änderungen von Variablen synchronisieren
Ach so hast Du gemeint. Jo, da hast Du natürlich recht. :)
Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:53 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