![]() |
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) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:55 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 by Thomas Breitkreuz