Autor Beitrag
reimo
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 75



BeitragVerfasst: Mi 24.09.08 14:54 
Hi!

Kann mir jemand sagen, was die beste Möglichkeit ist zwischen Threads zu kommunizieren?
Genauer zu meinem Problem:

Habe einen Thread welcher Statusdaten (ca 30 Byte ) bei Änderung an bis zu 10 andere Threads verschicken soll. Die anderen Threads melden sich zuerst bei diesem an, damit er weiß, an wen die Daten geschickt werden sollen.

Mein erster Ansatz:
Bei Änderung wird eine kurze Windows-Message an alle angemeldeten Threads geschickt und diese greifen daraufhin direkt auf eine globale Datenstruktur zu, welche die Daten enthält. Diese wäre natürlich durch Semaphore gesichert.
Der Nachteil ist ja, dass ständig durch irgendwen blockiert wird. Ist das wirklich so problematisch?

2ter Ansatz wäre:
Bei Änderung erzeuge ich eine Kopie der Statusdaten ( Objekt ) und verschicke an jeden Thread die Adresse dieser Objekte. Vorteil wäre, dass hier nichts blockiert, ich hab aber überhaupt keine Ahnung habe, wie es hier mit der Performance ausschaut, wenn dauernd neue Objekte erzeugt und deren Adressen verschickt werden.

Hier wie ich es mir vorstelle:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
For i := 0 to ListernerCount do begin

   // erzeugt eine kopie des Statusobjektes
   Status := CStatus.Create( RealtimeStatus );

   // hier wird der Status an den jeweiligen Thread geschickt (der Pointer zum Status-Objekt)
  //  der Thread muss dann das Objekt selber freigeben
  //  Listener ist ein Array mit den Thread-Handles
  FireEvent( Listener[i], Status );
end;

Könnt Ihr mir ein Paar Tipps geben, welcher dieser beiden Ansätze der bessere wäre? Oder überhaupt, wie sollte ich die Threads-Kommunikation lösen/angehen? Wie wird es normalerweise gemacht?

Danke!
baka0815
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 489
Erhaltene Danke: 14

Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
BeitragVerfasst: Mi 24.09.08 15:18 
user profile iconreimo hat folgendes geschrieben:
Mein erster Ansatz:
Bei Änderung wird eine kurze Windows-Message an alle angemeldeten Threads geschickt und diese greifen daraufhin direkt auf eine globale Datenstruktur zu, welche die Daten enthält. Diese wäre natürlich durch Semaphore gesichert.
Der Nachteil ist ja, dass ständig durch irgendwen blockiert wird. Ist das wirklich so problematisch?

Hier ist das Problem, dass du nicht weißt, ob die Nachricht bereits von allen Threads abgerufen wurde. Wenn nun ein (oder mehrere) Thread(s) den neuen Wert noch nicht abgerufen hat, du aber bereits eine neue Nachricht verschickst, weil es einen neuen Wert gibt. Dann kann es passieren, dass manche Threads das alte Ergebnis gar nicht mitbekommen.

Besser wäre (meiner Meinung nach) dein zweiter Ansatz bei dem du die Nachricht direkt an die anderen Threads verteilst.
Wenn die Threads deine Daten nur lesen, nicht aber ändern sollen, brauchst du doch nicht an jeden Thread eine Kopie schicken, sondern kannst ruhig das Original verwenden. Dein erster Thread würde ja so lange blockieren, bis alle anderen Threads die Daten entgegengenommen (und evtl. verarbeitet) haben.

Lasse mich da aber gerne eines besseren belehren.
reimo Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 75



BeitragVerfasst: Mi 24.09.08 20:31 
Zitat:
Hier ist das Problem, dass du nicht weißt, ob die Nachricht bereits von allen Threads abgerufen wurde. Wenn nun ein (oder mehrere) Thread(s) den neuen Wert noch nicht abgerufen hat, du aber bereits eine neue Nachricht verschickst, weil es einen neuen Wert gibt. Dann kann es passieren, dass manche Threads das alte Ergebnis gar nicht mitbekommen.


Da hast natürlich recht! Daran hab ich gar nicht gedacht.

Zitat:
Besser wäre (meiner Meinung nach) dein zweiter Ansatz bei dem du die Nachricht direkt an die anderen Threads verteilst.
Wenn die Threads deine Daten nur lesen, nicht aber ändern sollen, brauchst du doch nicht an jeden Thread eine Kopie schicken, sondern kannst ruhig das Original verwenden. Dein erster Thread würde ja so lange blockieren, bis alle anderen Threads die Daten entgegengenommen (und evtl. verarbeitet) haben.


Wenn ich jetzt allen Threads den Pointer auf die gleiche kopie schicke, hab ich aber das Problem, dass ich nicht weiss, wann dieses Objekt freigegeben werden kann.
Wenn ich aber an jeden Thread eine eigene Kopie verschicke, wird vorausgesetzt, dass dieser den Speicher wieder freigibt.

Was gibt es noch für Möglichkeiten zwischen Threads zu kommunizieren? Hab etwas von Pipes gelesen, ist dies brauchbar?
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mi 24.09.08 21:46 
user profile iconreimo hat folgendes geschrieben:
Wenn ich jetzt allen Threads den Pointer auf die gleiche kopie schicke, hab ich aber das Problem, dass ich nicht weiss, wann dieses Objekt freigegeben werden kann.
Ooch. Einfach einen Zugriffszähler (oder Referenzzähler) implementieren. Du weisst ja, wieviele Threads die Daten abholen müssen. Bei jedem Zugriff wird der Zähler erhöht/erniedrigt. Der letzte Zugriff zerstört dann die Daten.

Damit man neue Daten bereitstellen kann, bevor der letzte Thread die Daten abgeholt hat, erstellt man einfach einen neuen Datencontainer und verschickt an alle Threads den neuen Container.

Eine Garbagecollection müsste dann alte Container wegschmeissen (falls ein Thread mal hängt).

_________________
Na denn, dann. Bis dann, denn.
reimo Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 75



BeitragVerfasst: Mi 24.09.08 22:33 
Zitat:
Ooch. Einfach einen Zugriffszähler (oder Referenzzähler) implementieren. Du weisst ja, wieviele Threads die Daten abholen müssen. Bei jedem Zugriff wird der Zähler erhöht/erniedrigt. Der letzte Zugriff zerstört dann die Daten.


ok, das leuchtet mir ein. schön würd ich finden, wenn sich das Objekt nach dem letzten Zugriff selbst vernichtet! So Auf die art:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
function CStatus.GetData: XXX
begin

// Data Handling
....

// decrement Zugriffszähler 
dec( iAccessCounter );

// zerstöre dich selbst, wenn Zähler auf 0
if iAccessCounter = 0 then Self.Free;

end


Ist so etwas möglich? dann könnte sich das Status-Objekt nach dem letzten Zugriff selbst zerstören.

Außerdem müsste das Objekt durch Semaphore gesichert werden, da ja alle Threds zugleich auf das selbe Objekt zugreifen könnten.

Zitat:

Damit man neue Daten bereitstellen kann, bevor der letzte Thread die Daten abgeholt hat, erstellt man einfach einen neuen Datencontainer und verschickt an alle Threads den neuen Container.


jup, so hab ich mir das auch gedacht.

Zitat:

Eine Garbagecollection müsste dann alte Container wegschmeissen (falls ein Thread mal hängt).


Was meinst mit "Garbagecollection "? Verwende Delphi4, gibt es in den neueren Versionen einen automatischen Garbage Collector?
Oder meinst, dass ich mich darum kümmern sollte, falls es zu Speicherlecks durch hängengebliebene Threads gibt?
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 25.09.08 00:24 
user profile iconreimo hat folgendes geschrieben:
Was meinst mit "Garbagecollection "? Verwende Delphi4, gibt es in den neueren Versionen einen automatischen Garbage Collector?
Oder meinst, dass ich mich darum kümmern sollte, falls es zu Speicherlecks durch hängengebliebene Threads gibt?
Er meint, dass du dich darum kümmern musst. Delphi selbst hat keinen Garbage Collector im eigentlichen Sinn, es gibt gewisse Automatiken (Referenzzähler, etc.) in die Richtung, aber das hat mit einem "richtigen" Garbage Collector wie in anderen Sprachen nicht so viel zu tun.
baka0815
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 489
Erhaltene Danke: 14

Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
BeitragVerfasst: Do 25.09.08 09:17 
user profile iconreimo hat folgendes geschrieben:
Zitat:
Besser wäre (meiner Meinung nach) dein zweiter Ansatz bei dem du die Nachricht direkt an die anderen Threads verteilst.
Wenn die Threads deine Daten nur lesen, nicht aber ändern sollen, brauchst du doch nicht an jeden Thread eine Kopie schicken, sondern kannst ruhig das Original verwenden. Dein erster Thread würde ja so lange blockieren, bis alle anderen Threads die Daten entgegengenommen (und evtl. verarbeitet) haben.


Wenn ich jetzt allen Threads den Pointer auf die gleiche kopie schicke, hab ich aber das Problem, dass ich nicht weiss, wann dieses Objekt freigegeben werden kann.
Wenn ich aber an jeden Thread eine eigene Kopie verschicke, wird vorausgesetzt, dass dieser den Speicher wieder freigibt.

Was gibt es noch für Möglichkeiten zwischen Threads zu kommunizieren? Hab etwas von Pipes gelesen, ist dies brauchbar?

Die Kommunikation brauchst du doch hier gar nicht.

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Objekt := TTollesObjekt.Create;
// füllen
for I := 0 to Listener.Count - 1 do
begin
  Listener.Items[I].Event(Objekt);
end;
Objekt.Free;


Hier schickst du doch das Objekt über den Event direkt an den Thread. Dieser selbst kann sich dann eine lokale Kopie erstellen oder sich die benötigten Werte aus dem Objekt holen, etc. pp.
Solange die Event-Methode des Listener-Threads jedoch nicht abgearbeitet wurde, kommt die Methode doch auch gar nicht zurück und blockiert somit deine Schleife.
Bevor also nicht alle Listenere das Objekt entgegen genommen haben, ist die Schleife doch gar nicht zu ende und somit kannst du nach Ablauf der Schleife (also nachdem alle benachrichtigt wurden) das Objekt freigeben. Wenn sich einer der Listener-Threads allerdings keine Kopie anlegt, sondern mit der Referenz arbeitet, fällt der auf die Nase, aber da reicht ein entsprechender Hinweis in der Doku.
delphiprogrammierer
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 53



BeitragVerfasst: Do 25.09.08 10:23 
Ich schlage vor in jedem Thread eine Art Queue zu machen, in der die Daten reingestellt werden. Der Thread selbst holt sich dann stück für stück die daten aus der queue und freed das objekt selbst wenn es nicht mehr benötigt wird.

Moderiert von user profile iconNarses: Komplett-Zitat des letzen Beitrags entfernt
reimo Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 75



BeitragVerfasst: Do 25.09.08 10:24 
@baka0815

ok, das geht natürlich. in deinem Beispiel ist die "Event" - Methode, eine Methode der Listender-Threads und blockiert den Mainthread, solange die Daten nicht entgegengenommen wurden. Ist aber kein Problem, da diese wie du schon geschrieben hast, einfach nur eine Kopie des Objektes zur späteren Verarbeitung anlegen könnten.
Dies löst natürlich das Problem, wann das Status-Objekt freigegeben werden soll.

Mit meiner Idee hätte ich nämlich das Problem, dass ich nicht weiss, wann ich das Status-Objekt wieder löschen kann.
Habe zuerst darüber nachgedacht die Notification über eine PostThreadMessage oder PostMessage - Funktion zu lösen, da ich so blockierungsfrei bleiben würde:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
Mainthread.FireEvent( ThreadId: Integer; pStatus: CStatus )
begin
   PostThreadMessage( ThreadId, C_STATUS_MSG, Integer(Pointer(pStatus)), 0 );
end;

.....
.....
Mainthread.StatusChanged;
begin

 // erzeugt eine kopie des Statusobjektes
 Status := CStatus.Create( RealtimeStatus );

  For i := 0 to ListernerCount do begin
     // hier wird der Status an den jeweiligen Thread geschickt (der Pointer zum Status-Objekt)
    //  der Thread muss dann das Objekt selber freigeben
    //  Listener ist ein Array mit den Thread-Handles
    FireEvent( Listener[i], Status );
  end;

end;

deine Lösung über deine Hook-Funktion gefällt mir aber, falls ich sie richtig verstanden hab.

In deiner Lösung müsste ich aber in jedem Listener-Thread einen FIFO für die Messages einbauen. Die Event-Methode würde dann die Kopien der Objekte in diesen FIFO schieben. Es könnte ja sein, dass bereits eine Neue Status-Message empfangen wurde, noch bevor eine alte fertig verarbeitet worden ist. (Ist das richtig???)

---Moderiert von user profile iconNarses: Beiträge zusammengefasst---

user profile icondelphiprogrammierer hat folgendes geschrieben:

Ich schlage vor in jedem Thread eine Art Queue zu machen, in der die Daten reingestellt werden. Der Thread selbst holt sich dann stück für stück die daten aus der queue und freed das objekt selbst wenn es nicht mehr benötigt wird.


warst schneller! das meint ich mit dem FIFO in meiner Antwort. Sonst hab ich ja natürlich das Problem, dass ich nicht weiss, ob der Thread mit dem Verarbeiten der DAten schon fertig ist.

EDIT: Mit PostThreadMessage würd ich mir aber genau das ersparen, weil die queue vom OS geregelt wird.