Threads: sichere Übergabe von Variablen an Form

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
lazarusjulian
Beiträge: 39
Registriert: Mi 6. Jan 2016, 21:45

Threads: sichere Übergabe von Variablen an Form

Beitrag von lazarusjulian »

Hallo,

ich weiß es gibt schon viel zum Thema Threads und Variablen, dennoch würde ich gerne wissen,
welche Methode für meine Zwecke am besten geeignet wäre.
Vorhaben: Eine String-Variable soll durch einen Thread von einer Webseite ausgelesen werden und dann in einem Label der Form angezeigt werden.

Fragen:
(1) Muss ich Synchronize zwingend benutzen?
(2) Wäre die Verwendung einer globalen Variable ohne Synchronize Threadsicher? (Ich weiß sicher, dass es etwas dreckig wäre, aber kann es threadsicher sein?)
(Wenn der Thread nur schreibend, die Form nur lesend auf die variable zugreift)
(3) Ich habe auch etwas über die Verwendung von Windows.Sendmessage und Postmessage gelesen. Wäre dies eine Threadsichere Alternative zu Synchronize? Scheinbar kann man aber nur integer übergeben.
(4) Ich habe ein TImage als Ladeanimation und lade dort aus einer Imagelist hintereinander bilder. Das sollte also besser mit einem Timer, statt mit einem Thread gemacht werden?


Ich bitte um Entschuldigung für diese vielleicht etwas dreisten Fragen. Aber ich habe mich nun doch schon eine Weile belesen und werde nicht so richtig schlau.
Ich würde mich über konstruktive Antworten sehr freuen.

Viele Grüße,
Julian

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von Warf »

Schau dir mal das an: Link

lazarusjulian
Beiträge: 39
Registriert: Mi 6. Jan 2016, 21:45

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von lazarusjulian »

Danke, leider bin ich zu dusselig, die unit TestQueueAsyncCall zum laufen zu bringen.
//Edit: Hab es nun doch durch umbenennen zum laufen bekommen, trotzdem würde mich interessieren wie man es einfacher hinbekommt.

Das geht bestimmt irgendwie einfacher, oder?
Binde ich die Unit in ein neues Programm ein kommt die Meldung 'TestQueueAsyncCallform' nicht gefunden.
Nehme ich ein neues Program und benenne alle Bezeichner (Form1 etc) um in TestQueueAsyncCallForm klappts leider auch nicht.

//Edit2: Die Funktion QueueAsyncCall erwartet als zweites Argument einen PtrInt. Damit ist mir nicht geholfen, oder? Ich möchte doch gerne einen String übergeben.
Oder sehe ich das falsch?

LG Julian
Zuletzt geändert von lazarusjulian am Di 22. Jan 2019, 23:11, insgesamt 1-mal geändert.

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von Socke »

(1) Muss ich Synchronize zwingend benutzen?
Nein, ist aber die einfachste Möglichkeit um Daten zwischen einem Thread und dem Hauptthread auszutauschen. Sollte dein Thread nach Synchronize nichts weiter mehr tun (und auch keinen Speicher mehr freigeben), wäre TThread.Queue eine Alternative (läuft asynchron).

(2) Wäre die Verwendung einer globalen Variable ohne Synchronize Threadsicher?
Wenn du CriticalSections verwendest: ja. Ansonsten muss man aufpassen, wie verschiedene Programmteile auf die Variable zugreifen. Simple Datentypen (Ganzzahlen, Character, Fließkomma, Set) sind vergleichsweise unkritisch, da der Datenzugriff in einem atomar ist. Bei allem anderen (auch Strings!) kann man viele Fehler machen, die man nicht direkt sieht.

(4) Ich habe ein TImage als Ladeanimation und lade dort aus einer Imagelist hintereinander bilder. Das sollte also besser mit einem Timer, statt mit einem Thread gemacht werden?
Zum wechseln der Bilder kannst du keinen Thread verwenden. Hier ist der Timer korrekt.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von mschnell »

Wenn es Sprach-Elemente wie TThread.Queue gibt, sollte man natürlich explizite Betriebssystem-Funktionen (wie Windoes Messages) vermeiden !

Wenn es genau ein String ist (d.h. es ist sichergestellt, dass keine neuen Daten vom Thread empfangen werden, solange die vorigen noch vom Mainthread in Bearbeitung sind, kannst Du einen String nehmen und mit TThread.Queue dem Mainthread mitteilen, dass die Daten bereitstehen. Wo der String angelegt ist (global oder als Element Deiner TThread Komponente oder sonst wie, ist dann egal (solange der TThread nicht gefreed wird).

Wenn Du aber parallel mit dem, Abarbeiten noch Daten empfangen willst, brauchst Du ein FiFo für Deine Transfer-Strings. Das kannst Du z.B. mit einer TStringList machen und deren Verwaltung immer mit einer TCriticalSection schützen.

Am elegantesten ist eine Transfer-Klasse, die den String als Element enthält. Die wird vom Thread für jeden neuen String instantiiert und und hat eine "Execute" Procedure, die per TThread.Queue an den Mainthread übergeben wird. Am Ende macht die Execute Procedure "Free" und löscht sich damit selber. Dann brauchst Du keine Critical Section, weil paralleler Zugriff auf das jeweilige Objekt ausgeschlossen ist.

Alles was irgendwie die GUI betrifft (z.B. ein TImage, das angezeigt wird) darf auf keinen Fall in einem Thread gemacht werden !! (Aber natürlich in einer per TThread.Queue übergebenen Funktion.)

"Nur lesen" geht bei einem String nicht, weil er einen Referenz-Counter hat, der im Hintergrund bei allen Zugriffen verändert werden kann.

-Michael

lazarusjulian
Beiträge: 39
Registriert: Mi 6. Jan 2016, 21:45

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von lazarusjulian »

Hallo Michael, vielen Dank für deine Antwort.

Leider bin ich denke ich noch nicht so weit, dass ich deine Vorschläge umsetzen könnte, bzw. fehlen mir dazu noch mehr Infos wie Wiki-Einträge oder Quellcode-Schnipsel.
Das mag sich anhören, als ob ich es mir zu leicht machen würde, leider entspricht es der Wahrheit.

Ohne Jammern zu wollen, auch wenn du von Methoden wie Windows Messages abrätst, bin ich zumindest in der Lage dieses zu implementieren, da es sehr simpel ist.
Ich würde zu gerne eine Transfer-Klasse implementieren und ich werde dieses auch versuchen, ab einer gewissen Frustrationsrate neige ich aber leider wirklich dazu, es mir einfach zu machen.
Ich würde mir auch wünschen, dass es z.B. in Wiki-Einträgen auch Infos zu Implementationen wie einer von dir vorgeschlagenen Tranfserklasse gibt. Ich bin ja nun sicher nicht der erste,
der Daten aus Threads an die GUI übergeben möchte.
Ist die Antwort von "Runner" aus diesem Link das, was du mit der FIFO-Variante meinst?
https://stackoverflow.com/questions/191 ... cl-thread'

EVtl. reicht für meine Zwecke aber auch wirklich Synchronize (dieses habe ich derzeit aus dem Wiki implementiert) oder TThread.Queue. Kannst du mir erklären, wo der Unterschied dieser beiden Funktionen liegt?
Ich habe z.B. gelesen, dass Synchronize den nicht-GUI Thread stoppt, bis die Dinge im GUI-Thread ausgeführt wurden, während TThread.Queue dies nicht macht.

Außerdem bin ich immer noch nicht schlau geworden, ob QueueAsyncCall wirklich nur Integer-Variablen übergeben kann.

Fazit: ich bin von der Vielzahl der Möglichkeiten erschlagen. Ich möchte es wirklich gern richtig machen, benötige dazu aber weitere Hilfe (die ich natürlich nicht erwarten kann!)

Viele Grüße, Julian
(P.S. seid bitte nicht zu hart mit mir. Ich hatte einen schweren Motorradunfall, mir wurde die Vorfahrt genommen. Ich habe ein Bein verloren, bin krankgeschrieben und die Programmierung ist das Einzige, was mich noch am Leben hält.)

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6198
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von af0815 »

BTW: Es gibt in Lazarus einen Ordner mit Examples, auf den man über des Menü Tools-> Example Projects (in Deutsch halt so ähnlich) zugreifen kann. Meistens gibt es dort Antworten zu vielen Fragen. Gib dort im Suchfenster man 'thr' ein. Ich glaube, das in dem Examplecode einiges für deinen Fragen als Antworten zu finden ist.

Vor allen hat jeder diese Beispiele, also wenn du eine Frage zu einem Beispiel hast, so lässt sich das leichter beantworten.

PS: Achtung in Foren herrscht manchmal ein rauher Ton. Hier ist es relativ gesittet :-) (Danke Mods) Aber Programmieren kann manchmal recht frustrierend sein :-) :evil: 8)

PPS: Ich kenne das mit dem natürlichen Feind des Motorradfahrers. Ich habe bei meinem Crash alles behalten, aber dafür später vom Körper ein paar andere Rechnungen für den Streß bekommen. Erkenntnis: Eine Intensivstation ist einer der Orte wo wann über die wirklich wichtigen Dinge im Leben nachdenken kann. Und Leben allein (auch mit Einschränkungen) ist sehr viel Wert !!! Auch wenn man Materiell durch ein solches Ereignis gezwungen sein kann, sich auf ein absolutes minimum Einschränken muß.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

lazarusjulian
Beiträge: 39
Registriert: Mi 6. Jan 2016, 21:45

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von lazarusjulian »

Danke, den Beispielordner von Lazarus kannte ich noch nicht.
Mir ist noch eine weitere Sache zum Thema Threads eingefallen. Ich hab vor kurzem nen File Explorer gebaut, dort hab ich einen Thread die Ordner/Dateiliste zusammenstellen lassen und danach per Thread die Listview gefüllt.
Ich dachte das ist gut, weil die GUI dann ansprechbar bleibt während Dateien gesucht werden. Aber offensichtlich war auch das total falsch - Wie würde man es richtig machen?

LG Julian

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von Socke »

lazarusjulian hat geschrieben:Mir ist noch eine weitere Sache zum Thema Threads eingefallen. Ich hab vor kurzem nen File Explorer gebaut, dort hab ich einen Thread die Ordner/Dateiliste zusammenstellen lassen und danach per Thread die Listview gefüllt.
Ich dachte das ist gut, weil die GUI dann ansprechbar bleibt während Dateien gesucht werden. Aber offensichtlich war auch das total falsch - Wie würde man es richtig machen?

Im Thread lädst du deine Dateiliste und alle weiteren Informationen, z.B. Dateigröße, Icons etc. Wenn alles fertig ist, wird es an den Hauptthread übergenen; dieser befüllt dann erst die Listview.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von mschnell »

lazarusjulian hat geschrieben:ab einer gewissen Frustrationsrate neige ich aber leider wirklich dazu, es mir einfach zu machen.

Gerade bei Threads git die Gleichung

(unüberlegt) einfach = Höllen Ärger

Dafür ist das Umfeld einfach zu komplex.

Übrigens: TThread.Queue macht exakt dasselbe wie eine Windows Message, nur eben nicht Betriebssystem-spezifisch.

Bei TThread.Queue wird die Funktion im Mainthread parallel zum aufrufenden Thread gestartet. Bei TThread.Synchronize wartet der Thread bis die Funktion im Mainthread durchgelaufen ist. (Das sollte aber dokumentiert sein ! Ist in Delphi genauso und da ist es - seit einiger Zeit - dokumentiert.)

TThread.Queue übergibt mit der aufgerufenen Prozedur ein Objekt ("self") und damit alles, was daran hängt. (Aber Vorsicht Thread und Mainthread können parallel - und damit zerstörerisch - auf das Objekt zugreifen! Deshalb ist Synchronize und eine Transfer-Klasse weniger gefährlich.)

Schade, dass es keine offiziellen Beispiele oder eine Library-Implementierung für Transfer-Klassen gibt. Der Grund ist, dass man das idealer weise mit "Anonymen Funktionen" und "Finalizern" macht. Die werden bei Free Pascal seit längerem diskutiert, sind aber wohl noch nicht implementiert.

-Michael
Zuletzt geändert von mschnell am Mo 28. Jan 2019, 15:22, insgesamt 1-mal geändert.

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von kupferstecher »

Socke hat geschrieben:Simple Datentypen (Ganzzahlen, Character, Fließkomma, Set) sind vergleichsweise unkritisch, da der Datenzugriff in einem atomar ist. Bei allem anderen (auch Strings!) kann man viele Fehler machen, die man nicht direkt sieht.

Selbst bei simplen Datentypen kann man reinfallen. Ich habe mal einen Interlockmechanismus zwischen Threads mit Boolean Flags programmiert. D.h. die Flags sollten verhindern, dass beide Threads gleichzeitig eine Funktion aufrufen. Critical Section war mir zu langsam. Sporadisch hat sich das Programm dann aufgehängt. Problem ist wohl dass durch die Cache-Mechanismen verschiedene Threads mit lokalen Kopien der Variablen arbeiten. Geänderte Werte werden zwar automatisch übernommen, aber wohl zeitlich versetzt.

Der fehlerhafte Code sinngemäß:

Code: Alles auswählen

 
ReadLock,WriteLock: boolean
 
Procedure WaitBereitZuSchreiben
begin
  while ReadLock do; //Warten bis Lesen beendet
  WriteLock:= true;
  while ReadLock do; //nochmals warten, falls Readlock gleichzeitig mit WriteLock gesetzt wurde
end;
 
Procedure WaitBereitZuLesen
begin
 while WriteLock do; //Warten bis Lesen beendet
 ReadLock:= true;
  //kein nochmaliges Warten, sonst Deadlock. Lesen hat damit Priorität vor Schreiben
end;

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von mschnell »

Socke hat geschrieben: Problem ist wohl dass durch die Cache-Mechanismen verschiedene Threads mit lokalen Kopien der Variablen arbeiten. Geänderte Werte werden zwar automatisch übernommen, aber wohl zeitlich versetzt.

Bei Multiprozessor-Systemen werden die Caches nur synchronisiert, wenn es nötig ist. Eine Cache Synchronisation kann so lange dauern wie 1000 Befehle. Wenn man eine Cache Synchronisation erzwingen will muss man die entsprechenden "Atomic" Funktionen aufrufen, die der Compiler in Atomic Assembler Befehle umsetzt. Der Compiler garantiert ansonsten in keiner Weise, dass "einfache" Operationen Atomic ablaufen. Um eine schnelle Semaphore zu bauen verwendet man "FUTEX". Das ist eine Library-Funktion, die in Linux automatisch durch Verwendung der Posix threadlib angesprochen wird. Ich hoffe, dass Lazarus TThread.CriticalSection die benutzt. Bei Windows hofft man etwas ähnliches - da sind die System Libraries ja dynamisch und leichter von Lazarus verwendbar.

In x86: bei einem einfachen Statement generiert der Compiler (vermutlich) "inc" und das macht ein read/modify/write in einem Befehl, und ist bei einem Prozessor atomic, bei mehreren Prozessoren, wegen der fehlenden Cache Synchronisation aber nicht. Da braucht man "lock inc", und das dauert viele länger.

-Michael
Zuletzt geändert von mschnell am Mi 30. Jan 2019, 15:24, insgesamt 2-mal geändert.

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von kupferstecher »

@mschnell:
Danke für die Details! Threadprogrammierung ist halt doch tückischer, wie es zunächst scheint.

lazarusjulian
Beiträge: 39
Registriert: Mi 6. Jan 2016, 21:45

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von lazarusjulian »

Jetzt ist mir noch ne Frage eingefallen:
Und zwar bin ich auf GetCurrentThreadID gestoßen.

Die Frage: Wenn ich auf eine Variable ausschließlich mit derselben Thread ID zugreife, dann bedeutet das doch, dass für diese Variable kein Multithreading aktiv ist oder dieses korrekt behandelt ist und somit 'alles in Ordnung' ist, oder?

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Threads: sichere Übergabe von Variablen an Form

Beitrag von mschnell »

Klar,
Wenn nur ein Thread zugreift, gibt es keine Probleme. Dazu brauchst Du aber die Thread ID eigentlich nicht. Bei TThread ist die Thread-ID mit der Instanz der TThread-Nachfolger-Klasse "verheiratet". Objekt-Funktionen sind deshalb meist sinnvoller als die Thread-ID abzufragen (weil dann der immer vorhandene Self-Pointer dem Thread entspricht, solange Du nicht eine solche Funktion von einem anderen Thread - oder dem Main-Therad - aufrufst).

Aber:
Der Ort, wo die Variable deklariert ist, hat nichts mit der Threadfestigkeit zu tun. Du musst streng zwischen der "Speicher-Verwaltung" (TThread Objekt) und dem Zugriff durch einen gerade laufenden Thread unterscheiden. Wenn Du natürlich eine Klasse von TThread ableitest und dann mehrfach instanziierst (".Create") gibt es die Variable mehrfach und die verschiedenen Instanzen sind natürlich unabhängig von einander, obwohl sie gleich heißen-.

Wenn mehrere Threads (inklusive Main Thread) auf physikalisch dieselbe Variable "lesend und später schreibend" zugreifen, muss der Zugriff gegen konkurrierende Zugriffe mit "Atomic" oder "TCriticalSection" geschützt werden. Ausnahme: "Threadvar" diese globalen Variablen werden pro Thread einmal angelegt: jeder Thread hat seine eigene.

-Michael

Antworten