![]() |
Record threadsicher verwenden
Hallo,
ich habe eine Konsolenanwendung mit einem Record in einem Array, welches von diversen Threads verwendet werden soll. Sowohl lesen als auch schreiben. Das Array wird von einem eigenen Thread abgearbeitet und von anderer Seite wieder aufgefüllt. Es ist also eine Queue. Der Queue Thread sieht wie folgt aus:
Delphi-Quellcode:
Die Threads zum Auffüllen berücksichtigen den Arraylock ebenfalls.type Tsend_entry=record Frame:TWebSocketFrame; recipients:THttpServerConnectionIDDynArray; end; var send_queue:array of Tsend_entry; procedure DeleteArrayElement(const AIndex: Integer); begin Move(send_queue[AIndex + 1], send_queue[AIndex], SizeOf(send_queue[0]) * (Length(send_queue) - AIndex - 1)); //Dahinterliegende Daten aufrücken SetLength(send_queue, Length(send_queue) - 1); // Länge kürzen end; procedure TQueueSendThread.Execute; begin while not stop_thread do begin sleep(100); if high(send_queue)>=0 then if not array_lock then begin array_lock:=true; //beides muss zusammen ausgeführt werden sleep(10); server.WebSocketBroadcast( send_queue[0].frame,send_queue[0].recipients); DeleteArrayElement(0); array_lock:=false; end; end; end; Theoretisch ist das Array also gesperrt wenn es in schreibender Verwendung ist. Dennoch habe ich mit Problemen zu kämpfen, wo ich sagen würde, das es mit dem Timing zusammen hängt. Denn zwischen prüfung ob der Lock gesetzt ist und anschließenden setzen, könnte ein anderer Thread zuvorkommen. Zuvor hatte ich folgendes versucht:
Delphi-Quellcode:
Aber das klappte gar nicht erst.
server.WebSocketBroadcast( send_queue[0].frame,send_queue[0].recipients);
TThread.Synchronize(nil, procedure begin DeleteArrayElement(0); end); Wie kann ich den Zugriff threadsicher gestalten? Oder liegt es vielleicht auch schon daran, das ich den Arraylock nur beim schreiben setze? |
AW: Record threadsicher verwenden
Mit einem Objekt der Klasse TMultiReadExclusiveWriteSynchronizer aus System.SysUtils kannst Du den Lese und Schreibzugriff steuern.
Aufpassen: BeginWrite gibt einen Boolean zurück, der besagt aber nur, dass evtl. gelesene Variablen ungültig geworden sein könnten. Die Funktion Synchronize ist der Anfang von vielem Übel, am besten nicht verwenden. |
AW: Record threadsicher verwenden
|
AW: Record threadsicher verwenden
Statt Deinem ArrayLock kannst Du auch TMonitor.TryEnter(EinObjekt) verwenden.
|
AW: Record threadsicher verwenden
![]() Add, Remove, LockList/UnlockList Schade, dass es hier kein Push/Pop wie in ![]() ![]() Und jupp, "dieses" TMonitor ist eine Art CriticalSection, die man "in" jedes TObjekt legen kann. (nicht zu Verwechseln mit TMonitor für den Bildschirm ... der Name wurde dämlicher Weise so von C# geklaut) Hat jeder Thread seinen eigenen Server? Wenn nicht, dann aufpassen, ob WebSocketBroadcast thread-save ist und wenn nicht, dann muß das auch synchronisiert werden. |
AW: Record threadsicher verwenden
PS:
Delphi-Quellcode:
Jupp, es muß "zusammen", denn rate mal was passiert, wenn zwei threads gleichzeitig das IF prüfen, noch bevor Einer das auf True gestellt hat ... dann sind zwei Drin.
if not array_lock then
begin array_lock:=true; //beides muss zusammen ausgeführt werden Lösung: * eine "richtige" Sperre drumrum (CriticalSection, MultiReaderWriter, ...) * oder als "atomare" Operation das Vergleichen+Zuweisen, wie z.B. ![]() ![]() |
AW: Record threadsicher verwenden
Da du mORMot verwendest, schaue in die Unit SynTable. Dort findest du die Klasse TSynQueue.
Code:
Ein Beispiel für die Anwendung findest du in der Unit SynSelfTests.
/// thread-safe FIFO (First-In-First-Out) in-order queue of records
// - uses internally a dynamic array storage, with a sliding algorithm Oder in der Unit SynCommons die Klasse TSynDictionary. Bis bald... Thomas |
AW: Record threadsicher verwenden
Also mit TSynQueue hat es super geklappt. Danke für den Tipp.
Aber ich suche noch etwas für meine Userliste. Der Gedanke wäre eine TThreadlist welche folgendes verwaltet.
Delphi-Quellcode:
type TUser=record
ID:int64; UserName:string; LoginCount:integer; end; Jedoch sind alle Infos die ich dazu gesehen habe mit Klassen oder Objekten gemacht. Ich bekomme es gerade absolut nicht hin da ein Record einzufügen. Mit einer Variablen welche auf den Record pointet klappt das einfügen.
Delphi-Quellcode:
Wenn ich ptUser.UserName vorher etwas zuweise und dann über list[0] versuche das zurückzulesen, gibt es ne Schutzverletzung.
var ptUser:^TUser;
... List := Userlist.LockList; List.Add(ptUser); ... Naja, irgendwie klar, denn ptUser pointet ja nur auf den Typ. Oder muss ich von TUser ein Array erstellen und dessen Elemente der Threadlist zuweisen? EDIT Habe noch etwas gefunden wonach ich zunächst "new(ptUser)" aufrufen muss. Mir ist aber noch nicht ganz klar, wo sich das erzeugte Objekt dann befindet und wie ich es erreiche. Vor allem wie ich dann z.B. mit List[0] wieder auf den Typ TUser komme. |
AW: Record threadsicher verwenden
Zitat:
Delphi-Quellcode:
Mit der Angabe TimeoutSeconds kannst du durch Aufruf von DeleteDeprecated() ältere Einträge rausschmeißen (hier 1 Stunde).
type
TUser = record UserName: String; LoginCount: Integer; end; TUserID = Int64; TUserIDDynArray = array of TUserID; TUserDynArray = array of TUser; FUserList := TSynDictionary.Create(TypeInfo(TUserIDDynArray), TypeInfo(TUserDynArray), False, {TimeoutSeconds=} 3600); var user: TUser; begin user.UserName := ''; user.LoginCount := 2; FUserList.AddOrUpdate(userID, user); Bis bald... Thomas |
AW: Record threadsicher verwenden
Ich probiere das gerade aus. Soweit passt das TSynDictionary sehr gut in das Konzept.
Allerdings bin ich nun bei meiner Broadcastliste hängen geblieben, welche ich im Zuge der Umstrukturierung auch ändern sollte. Der Plan war/ist ein Array welches die ID's der eingeloggten User beinhaltet und nur bei Login/Logoff aktivität geupdatet wird. Es wird für jede ausgehende Nachricht verwendet. Der Zugriff darauf muss also auch threadsicher sein.
Delphi-Quellcode:
var logged_users:THttpServerConnectionIDDynArray;
Falls ich das Array nicht threadsicher führen kann, besteht die Möglichkeit sie aus dem TSynDictionary abzuleiten. Aber es will mir nicht gefallen, für jede Nachricht diesen aufwändigen Prozess zu wiederholen. Ich kann mir aus dem TSynDictionary die Values herausschreiben lassen um an die potenziellen Benutzer zu kommen, jedoch fehlt mit der Key dazu. Oder anders ausgedrückt:
Delphi-Quellcode:
Ich würde dann jeden Key benötigen, wo der UserName<>'' ist.
type
TUser = record UserName: String; LoginCount: Integer; end; TUserID = Int64; TUserIDDynArray = array of TUserID; //Key TUserDynArray = array of TUser; //Value Theoretisch könnte ich jetzt auch wieder ein TSynDictionary dafür missbrauchen indem ich key=Value setze. Oder aber ich füge dem bestehenden TSynDictionary bei TUser noch die ID hinzu. Nur das ist dann ja auch irgendwie doppelt gemoppelt wenn der Key nochmals in der Struktur der Value auftaucht. Oder eben eine TThreradlist für das THttpServerConnectionIDDynArray. (Sofern ich es gebacken bekomme das darin abzubilden) |
AW: Record threadsicher verwenden
Zitat:
Delphi-Quellcode:
Oder du nutzt die Funktion FUserList.ForEach().
var
user: TUser; userID: TUserID; begin userID := 5; user.UserName := 'Klaus'; user.LoginCount := 3; FUserList.AddOrUpdate(userID, user); userID := 0; FUserList.FindKeyFromValue(user, userID); // userID = 5 Bis bald... Thomas |
AW: Record threadsicher verwenden
Zitat:
anstatt ein solches Array für JEDE Nachricht aus dem TSynDictionary neu aufzustellen. Im Grunde resultiert das zweite Problem aus dem ersten: Nutze ich das
Delphi-Quellcode:
dann ist es nicht threadsicher. Aber es ist "sofort" verfügbar.
var logged_users:THttpServerConnectionIDDynArray;
Wenn ich das Array für jede Nachricht aus dem TSynDictionary zusammen baue, fürchte ich eine hohe Systemlast. Und zusätzlich habe ich dann das folgende Problem: Die Nachricht darf nur an User versendet werden, dessen Name hinterlegt ist. Somit müsste ich das ganze TSynDictionary durchgehen und prüfen, ob da ein Username drin steht. Und wenn, dann brauche ich den Key dazu. Deswegen wird mir das nicht helfen:
Delphi-Quellcode:
Der User 'Klaus' aus dem Beispiel kann ja von verschiedenen Endgeräten eingeloggt sein. Er hat demnach verschiedene ID's.
FUserList.FindKeyFromValue(user, userID); // userID = 5
Mein erster Ansatz war:
Delphi-Quellcode:
FUserList.CopyValues(users);
for I := Low(users) to High(users) do if (users[i].UserName<>'') then //Alle eingeloggten User begin //Und hier komme ich nicht an die ID, da sie der KEY ist und ich keinen Bezug dazu habe end; |
AW: Record threadsicher verwenden
Ich antworte allgemein. Wenn du noch kein gutes Gefühl für die richtige Architektur hast, probiere zuerst die einfachste Lösung aus: "make it work, then make it fast".
TDynArray/TDynArrayHashed sind nur die "mächtige Verwaltung" eines array of x. Values und Keys in einem TSynDictionary sind TDynArray/TDynArrayHashed Arrays. Wenn du den Array-Index des Values hast, hast du auch den Array-Index des Keys. TSynDictionary ermöglicht den direkten Zugriff auf die Values. Count gibt den Füllstand zurück. Mit Values.ElemPtr(idx)^ greifst du zu. Und Threadsafe wird es mit einem Lock/UnLock um alles. Die Ausführungszeit dürfte sich im Bereich us Sekunden bewegen. Du kannst die benötigten IDs damit zusammensammeln. Wenn dich die genaue Zeit interessiert:
Delphi-Quellcode:
Bis bald...
var
Timer: TPrecisionTimer; begin Timer.Start; ... ShowMessage(Format('Total time: %s', [Timer.Stop])); end; Thomas |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:00 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