Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Record threadsicher verwenden (https://www.delphipraxis.net/207342-record-threadsicher-verwenden.html)

MechMac666 15. Mär 2021 14:44

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:



 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;
Die Threads zum Auffüllen berücksichtigen den Arraylock ebenfalls.
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:
      server.WebSocketBroadcast( send_queue[0].frame,send_queue[0].recipients);
      TThread.Synchronize(nil, procedure
      begin
        DeleteArrayElement(0);
      end);
Aber das klappte gar nicht erst.


Wie kann ich den Zugriff threadsicher gestalten?
Oder liegt es vielleicht auch schon daran, das ich den Arraylock nur beim schreiben setze?

jziersch 15. Mär 2021 14:49

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.

Der schöne Günther 15. Mär 2021 14:50

AW: Record threadsicher verwenden
 
Nicht das Rad neu erfinden.

http://docwiki.embarcadero.com/RADSt...riff_vermeiden

jziersch 15. Mär 2021 14:54

AW: Record threadsicher verwenden
 
Statt Deinem ArrayLock kannst Du auch TMonitor.TryEnter(EinObjekt) verwenden.

himitsu 15. Mär 2021 14:55

AW: Record threadsicher verwenden
 
Delphi-Referenz durchsuchenTThreadList<Tsend_entry> , da ist gleich alles für die Synchronisierung integriert.


Add, Remove, LockList/UnlockList


Schade, dass es hier kein Push/Pop wie in Delphi-Referenz durchsuchenTStack bzw. Delphi-Referenz durchsuchenTQueue gibt.


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.

himitsu 15. Mär 2021 15:05

AW: Record threadsicher verwenden
 
PS:
Delphi-Quellcode:
if not array_lock then
begin
  array_lock:=true; //beides muss zusammen ausgeführt werden
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.

Lösung:
* eine "richtige" Sperre drumrum (CriticalSection, MultiReaderWriter, ...)
* oder als "atomare" Operation das Vergleichen+Zuweisen, wie z.B. MSDN-Library durchsuchenInterlockedCompareExchange bzw. Delphi-Referenz durchsuchenAtomicCmpExchange (und LOCK im Assembler)

mytbo 15. Mär 2021 15:10

AW: Record threadsicher verwenden
 
Da du mORMot verwendest, schaue in die Unit SynTable. Dort findest du die Klasse TSynQueue.
Code:
/// thread-safe FIFO (First-In-First-Out) in-order queue of records
// - uses internally a dynamic array storage, with a sliding algorithm
Ein Beispiel für die Anwendung findest du in der Unit SynSelfTests.

Oder in der Unit SynCommons die Klasse TSynDictionary.

Bis bald...
Thomas

MechMac666 15. Mär 2021 21:21

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:
var ptUser:^TUser;
...
List := Userlist.LockList;
List.Add(ptUser);
...
Wenn ich ptUser.UserName vorher etwas zuweise und dann über list[0] versuche das zurückzulesen, gibt es ne Schutzverletzung.
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.

mytbo 15. Mär 2021 21:59

AW: Record threadsicher verwenden
 
Zitat:

Zitat von MechMac666 (Beitrag 1485226)
Aber ich suche noch etwas für meine Userliste.

Ich habe jetzt keine Möglichkeit zum Test, das sollte aber so funktionieren.
Delphi-Quellcode:
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);
Mit der Angabe TimeoutSeconds kannst du durch Aufruf von DeleteDeprecated() ältere Einträge rausschmeißen (hier 1 Stunde).

Bis bald...
Thomas

MechMac666 16. Mär 2021 21:17

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:
 type
  TUser = record
    UserName: String;
    LoginCount: Integer;
  end;

  TUserID = Int64;
  TUserIDDynArray = array of TUserID; //Key
  TUserDynArray = array of TUser;     //Value
Ich würde dann jeden Key benötigen, wo der UserName<>'' ist.


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 20:04 Uhr.
Seite 1 von 2  1 2      

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