| Autor |
Beitrag |
reimo
      
Beiträge: 75
|
Verfasst: 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:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| For i := 0 to ListernerCount do begin
Status := CStatus.Create( RealtimeStatus );
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
      
Beiträge: 489
Erhaltene Danke: 14
Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
|
Verfasst: Mi 24.09.08 15:18
reimo 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 
      
Beiträge: 75
|
Verfasst: 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
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Mi 24.09.08 21:46
reimo 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 
      
Beiträge: 75
|
Verfasst: 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:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| function CStatus.GetData: XXX begin
....
dec( iAccessCounter );
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
      
Beiträge: 19339
Erhaltene Danke: 1752
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 25.09.08 00:24
reimo 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
      
Beiträge: 489
Erhaltene Danke: 14
Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
|
Verfasst: Do 25.09.08 09:17
reimo 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.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7:
| Objekt := TTollesObjekt.Create; 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
      
Beiträge: 53
|
Verfasst: 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 Narses: Komplett-Zitat des letzen Beitrags entfernt
|
|
reimo 
      
Beiträge: 75
|
Verfasst: 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:
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
Status := CStatus.Create( RealtimeStatus );
For i := 0 to ListernerCount do begin 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 Narses: Beiträge zusammengefasst---
delphiprogrammierer 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.
|
|
|