Discussion:
Frage zu Threads, syncronize und critical section
(zu alt für eine Antwort)
Stefan
vor 13 Jahren
Permalink
Hallo,

erstmal vorab, ich selber programmiere kaum noch in Delphi. Mein
Schwerpunkt ist Mikrocontrollerprogrammierung in Assembler und C
(WinAVR/GCC). Wir kämpfen aber aktuell mit einem Programm eines
ehemaligen Kollegen. Problem ist, das Programm hängt sich in
unregelmäßigen Abständen auf. Das können einige Stunden sein, oder auch
mal einige Wochen. Wir haben dort die diversen Threads, die insbesondere
für TCP/IP Kommunikation verwendet werden im Verdacht.

Der Kollege hat an diversen Stellen mit Synchronize gearbeitet.

Die Problematik ist dabei bekannt, zwei Threads verwenden dieselben
Datenbestände und es muss verhindert werden, dass Thread 1 von Thread 2
unterbrochen wird, während gemeinsam verwendete Daten gelesen oder
verändert werden.

In GCC löse ich das, indem ich kurzfristig alle INTs sperre, z.B.

cli(); // alle INTs sperren
datenaendern();
sei(); // INTs wieder freigeben

Jetzt habe ich bei der Dokumentation zum Thema Threads den Eindruck,
dass ich entweder zu doof bin, oder dass diese von Leuten geschrieben
bzw. übersetzt wurde, die keine Ahnung vom Thema haben, deshalb hier
einige grundsätzliche Verständnisfragen.

Was genau macht syncronize()?

Der Kollege hat hier jeweils im Thread die Prozeduren, in denen Daten
verändert oder gelesen werden mit synchronize aufgerufen.

Ich bin der Meinung, eine critical section wäre hier besser, also


EnterCriticalSection(CritSect);
datenaendern;
LeaveCriticalSection(CritSect);

Passier dabei das selbe, wie in meinem Beispiel oben, oder was läuft da
genau ab?

Gruß

Stefan
Gunnar
vor 13 Jahren
Permalink
Moin Stefan,
Problem ist, das Programm hängt sich in unregelmäßigen Abständen auf.
Deadlock?
Was genau macht syncronize()?
Führt den übergebenen Methodenaufruf im Kontext des Haupt-Threads aus.
Ich bin der Meinung, eine critical section wäre hier besser,
Die VCL ist nicht Thread-sicher. Du musst also genau nachschauen, was in den
"Synchronize"-Aufrufen genau passiert.
Andererseits darf Synchronize nicht vom Haupt-Thread aus aufgerufen werden,
das kann einen Deadlock zur Folge haben.


Gruß
Gunnar
Stefan
vor 13 Jahren
Permalink
Post by Gunnar
Moin Stefan,
Problem ist, das Programm hängt sich in unregelmäßigen Abständen auf.
Deadlock?
Sieht so aus. Die CPU geht auf 100% Auslastung. Bei Mehrkernsystemen
geht eine CPU auf 100%. Das Anwendungsfenster wird nicht mehr
aktualisiert. Ein zweites Delphi-Programm (Client), dass mit dem ersten
(Server) kommuniziert hängt ebenfalls.
Post by Gunnar
Was genau macht syncronize()?
Führt den übergebenen Methodenaufruf im Kontext des Haupt-Threads aus.
Also der Thread wird an dieser Stelle unterbrochen und die Bearbeitung
der Methode läuft auf der "obersten Ebene" der Anwendung?
Post by Gunnar
Ich bin der Meinung, eine critical section wäre hier besser,
Die VCL ist nicht Thread-sicher. Du musst also genau nachschauen, was in
den "Synchronize"-Aufrufen genau passiert.
Ich hab mir nicht das komplette Programm durchgesehen,
Post by Gunnar
Andererseits darf Synchronize nicht vom Haupt-Thread aus aufgerufen
werden, das kann einen Deadlock zur Folge haben.
Das ist schon klar.

Gruß

Stefan
Gunnar
vor 13 Jahren
Permalink
Post by Gunnar
Post by Stefan
Was genau macht syncronize()?
Führt den übergebenen Methodenaufruf im Kontext des Haupt-Threads aus.
Also der Thread wird an dieser Stelle unterbrochen und die Bearbeitung der
Methode läuft auf der "obersten Ebene" der Anwendung?
Wenn Du so willst ja.
Stefan
vor 13 Jahren
Permalink
Post by Gunnar
Post by Stefan
Post by Gunnar
Post by Stefan
Was genau macht syncronize()?
Führt den übergebenen Methodenaufruf im Kontext des Haupt-Threads aus.
Also der Thread wird an dieser Stelle unterbrochen und die Bearbeitung
der Methode läuft auf der "obersten Ebene" der Anwendung?
Wenn Du so willst ja.
Ok, aber was bringt das?

Ist für die Zeit der Abarbeitung der Methode die Threadverarbeitung
gesperrt, oder kann die Methode von anderen Threads unterbrochen werden?


Gruß

Stefan
Hans-Peter Diettrich
vor 13 Jahren
Permalink
Post by Stefan
Ist für die Zeit der Abarbeitung der Methode die Threadverarbeitung
gesperrt, oder kann die Methode von anderen Threads unterbrochen werden?
Die Synchronized Aufrufe werden strikt sequentiell abgearbeitet, und
können nicht unterbrochen werden. Ansonsten laufen alle anderen Threads
ungebremst weiter, solange sie kein weiteres Synchronized absetzen.

DoDi
Arno Garrels
vor 13 Jahren
Permalink
Post by Stefan
Post by Gunnar
Post by Stefan
Post by Gunnar
Post by Stefan
Was genau macht syncronize()?
Führt den übergebenen Methodenaufruf im Kontext des Haupt-Threads aus.
Also der Thread wird an dieser Stelle unterbrochen und die
Bearbeitung der Methode läuft auf der "obersten Ebene" der
Anwendung?
Wenn Du so willst ja.
Ok, aber was bringt das?
Ist für die Zeit der Abarbeitung der Methode die Threadverarbeitung
gesperrt, oder kann die Methode von anderen Threads unterbrochen werden?
Ja, der aufrufende Thread wartet solange, bis der Aufruf von Synchronize
zurückkehrt ist. Die mit Synchronize an den Hauptthread übergegebene Methode
wird dann im Hauptthread ausgeführt wenn keine Messages mehr in der Queue
des Hauptthreads warten, also wenn die Anwendung idle (im Leerlauf) ist.
--
Arno
Arno Garrels
vor 13 Jahren
Permalink
...
Das "Ja" bezieht sich natürlich auf den ersten Teil deiner Frage :)
--
Arno
Hans-Peter Diettrich
vor 13 Jahren
Permalink
Post by Arno Garrels
Ja, der aufrufende Thread wartet solange, bis der Aufruf von Synchronize
zurückkehrt ist. Die mit Synchronize an den Hauptthread übergegebene Methode
wird dann im Hauptthread ausgeführt
ACK
Post by Arno Garrels
wenn keine Messages mehr in der Queue
des Hauptthreads warten, also wenn die Anwendung idle (im Leerlauf) ist.
Bist Du da sicher? Warum sollte Synchronize nicht wie jede andere
Message behandelt werden, oder sogar vorrangig vor allen anderen (GUI)
Messages?

DoDi
Soeren Muehlbauer
vor 13 Jahren
Permalink
Hi,
Post by Hans-Peter Diettrich
Post by Arno Garrels
wenn keine Messages mehr in der Queue
des Hauptthreads warten, also wenn die Anwendung idle (im Leerlauf) ist.
Bist Du da sicher? Warum sollte Synchronize nicht wie jede andere
Message behandelt werden, oder sogar vorrangig vor allen anderen (GUI)
Messages?
Weil Delphi eine WM_NULL Message an den Mainthread schickt. Soweit ich
weiss stellt Windows gewisse Botschaften immer an den Anfang der
Warteschlange. WM_Paint ist beispielsweise so eine. Wenn Deine Anwendung
(ein Control) also permanent neu zeichnet, kommt die WM_NULL Nachricht
eventuell nie an. In TApplication.ProcessMessage ist es sogar so, dass
die VCL Mausbotschaften zu allererst aus der Warteschlange nimmt. Das
ist gelegentlich sogar problematisch. Wir haben das hier gepatcht.
Natürlich könnte man das so machen, dass eine spezielle Nachricht zum
Synchronisieren geschickt wird (WM_NULL ist vielleicht ein bischen doof)
und diese zuerst aus der Warteschlange genommen wird. Danach müsste
natürlich die nächste reguläre Nachricht aus der Warteschlange
abgearbeitet werden damit nicht viele Synchronize's die GUI lahmlegen.

Sören
Arno Garrels
vor 13 Jahren
Permalink
Post by Soeren Muehlbauer
Hi,
Post by Hans-Peter Diettrich
Post by Arno Garrels
wenn keine Messages mehr in der Queue
des Hauptthreads warten, also wenn die Anwendung idle (im Leerlauf) ist.
Bist Du da sicher? Warum sollte Synchronize nicht wie jede andere
Message behandelt werden, oder sogar vorrangig vor allen anderen
(GUI) Messages?
Weil Delphi eine WM_NULL Message an den Mainthread schickt.
Richtig, bei neueren Delphiversionen (ab v7?).
Post by Soeren Muehlbauer
Soweit ich
weiss stellt Windows gewisse Botschaften immer an den Anfang der
Warteschlange. WM_Paint ist beispielsweise so eine.
WM_PAINT wird nicht wirklich in die Warteschlange gestellt. Sie
wird nur dann von GetMessage/PeekMessage abholt werden wenn keine
gepostete Message in der Schlange ist. Gut zu beobachten, wenn man
z.B. mit ICS und einem schnellen Netz im Hauptthread einen Download
macht. Dabei werden zwar jede Menge Messages verarbeitet aber WM_PAINT
nicht und die GUI scheint eingefroren.
Post by Soeren Muehlbauer
Wenn Deine
Anwendung (ein Control) also permanent neu zeichnet, kommt die
WM_NULL Nachricht eventuell nie an.
Ich denke nicht, WM_NULL wird ja tatsächlich gepostet, solche
Messages haben Vorrang vor WM_PAINT.
Post by Soeren Muehlbauer
In TApplication.ProcessMessage
ist es sogar so, dass die VCL Mausbotschaften zu allererst aus der
Warteschlange nimmt.
Die ist ähnlich wie WM_PAINT, würden solche Messages alle in die
Warteschlange gestellt werden, wäre die Schlange schnell voll
(standardmäßig 10000).
Post by Soeren Muehlbauer
Das ist gelegentlich sogar problematisch. Wir
haben das hier gepatcht.
Seit Delphi 2009 benutzt auch die VCL Filter mit PeekMessage(),
dadurch wird die Messageverarbeitung aber deutlich langsamer :(
Post by Soeren Muehlbauer
Natürlich könnte man das so machen, dass
eine spezielle Nachricht zum Synchronisieren geschickt wird (WM_NULL
ist vielleicht ein bischen doof)
Das war AFAIR vor Delphi 7 so, da wurde schlicht mit SendMessage
synchronisiert. Ein gepostetes WM_NULL ist völlig OK um den Thread ggf.
aufzuwecken, CheckSynchronize wird immer ausgeführt bevor idle.
In einer Console-Anwendung muss man ggf. CheckSynchronize selbst
aufrufen.
Post by Soeren Muehlbauer
und diese zuerst aus der
Warteschlange genommen wird. Danach müsste natürlich die nächste
reguläre Nachricht aus der Warteschlange abgearbeitet werden damit
nicht viele Synchronize's die GUI lahmlegen.
Es wird immer die Liste mit allen Synchronize-Items hintereinander
abgearbeitet.
--
Arno
Heiko Nocon
vor 13 Jahren
Permalink
Post by Stefan
Post by Gunnar
Deadlock?
Sieht so aus. Die CPU geht auf 100% Auslastung.
Das ist dann kein Deadlock. Bei Deadlocks hängen die beteiligten Threads
im Wartezustand und verbrauchen demzufolge keinerlei Rechenzeit.
--
Wer Komponenten ohne Quelltext oder richtig miese Komponenten
oder gute Komponenten mit Quelltext, ohne die Source zu verstehen, sich verschafft,
um sie in Form "eigener" Programme in Verkehr zu bringen,
der wird mit Gefängnis nicht unter 5 Jahren bestraft.
Hans-Peter Diettrich
vor 13 Jahren
Permalink
Post by Heiko Nocon
Post by Stefan
Post by Gunnar
Deadlock?
Sieht so aus. Die CPU geht auf 100% Auslastung.
Das ist dann kein Deadlock. Bei Deadlocks hängen die beteiligten Threads
im Wartezustand und verbrauchen demzufolge keinerlei Rechenzeit.
Das Verhalten im Wartezustand hängt davon ab, wie das Warten
implementiert ist. Gerade wenn ein Programm hängt, könnte die Ursache
eine schlechte Implementierung sein, mit Pollen statt WaitFor...

DoDi
Stefan Graf
vor 13 Jahren
Permalink
...
Synchronize ist eine üble Sache und nicht wirklich zu empfehlen. Kann
kann das auch selber schön über Messages lösen. Synchronizie macht das
auch in etwa so.

Datenermittlung ohne VCL im Thread und Signalisierung über Messages,
wenn es etwas abzuholen oder anzuzeigen gibt.

Das mit dem critical section geht auch, man läuft dabei nur Gefahr, das
bei einem Fehler der Thread für immer stehen bleibt. Also immer mit try
und finally einsetzen.

Es gibt dann auch noch die Semaphors, da kann man noch mehr mit machen
und die kann man den Status abfragen oder auf die Freigabe warten, was
bei critical section nicht geht.

PS: Interrupts bei einem Multitasking-OS zu sperren, kommt nicht sehr
gut ;-)
--
Stefan Graf
Alfred Gemsa
vor 13 Jahren
Permalink
Post by Stefan
Die Problematik ist dabei bekannt, zwei Threads verwenden dieselben
Datenbestände und es muss verhindert werden, dass Thread 1 von Thread 2
unterbrochen wird, während gemeinsam verwendete Daten gelesen oder
verändert werden.
Was genau macht syncronize()?
Der Kollege hat hier jeweils im Thread die Prozeduren, in denen Daten
verändert oder gelesen werden mit synchronize aufgerufen.
Ich bin der Meinung, eine critical section wäre hier besser, also
Also, normalerweise hast du eine GUI, wo der User was klicken kann, oder
wo irgendwas angezeigt wird, "die" Windows-Application halt.

Das läuft in einem Thread ("Hauptthread")ab.

Wenn du jetzt bestimmte Berechnungen oder IO-Reaktionen (TCP/IP) von der
GUI abkoppeln willst, lagerst du diese in einen oder mehrere
Nebenthreads aus.

Wenn du jetzt irgendwelche Ereignisse in diesen Nebenthreads auf der GUI
anzeigen willst (im "Hauptthread"), dann musst du das über eine Funktion
aus den Nebenthreads erledigen, die über Synchronize aufgerufen werden.
Das muss wegen des nicht thread-sicheren Ablaufs der VCL-Anteile so sein.

Das hat aber bis dahin nix mit dem gesicherten Zugriff auf gemeinsame
Datenstrukturen zu tun:

Solche Zugriffe müssen über CriticalSections oder mit Semaphoren
gegeneinander verriegelt werden, wie du schon richtig vermutest.

Ich verfahre möglichst immer so: In den Nebenthreads nicht auf
VCL-Elemente zugreifen, sondern Zustandsänderungen, die auf der GUI
angezeigt werden müssen, in public Variablen festhalten, auf die der
Hauptthread dann selbständig zugreift. Dadurch vermeidest du
Synchronize, und der eventuell zeitkritische Nebenthread läuft
vollständig unabhängig von der GUI und vielem Windows-Kram, der den
Hauptthread anhalten kann (zum Beipiel Rechtsklick auf die
Fensterleiste: Das bringt den Hauptthread zum Stillstand (und den
Nebenthread auch, wenn du in ihm irgednwas Synchronized)).

HTH,

Alfred
Stefan
vor 13 Jahren
Permalink
Post by Alfred Gemsa
Wenn du jetzt bestimmte Berechnungen oder IO-Reaktionen (TCP/IP) von der
GUI abkoppeln willst, lagerst du diese in einen oder mehrere
Nebenthreads aus.
Genau darum (TCP/IP) geht es. Normalerweise versuche ich Threads zu
vermeiden und in meinen Anwendungen habe ich die auch nie benötigt.
Post by Alfred Gemsa
Wenn du jetzt irgendwelche Ereignisse in diesen Nebenthreads auf der GUI
anzeigen willst (im "Hauptthread"), dann musst du das über eine Funktion
aus den Nebenthreads erledigen, die über Synchronize aufgerufen werden.
Das muss wegen des nicht thread-sicheren Ablaufs der VCL-Anteile so sein.
Ok, das ist eine interessante Information. Ich werde das (VCL) mal mit
dem Kollegen, der das Thema aktuell bearbeitet besprechen.
Post by Alfred Gemsa
Das hat aber bis dahin nix mit dem gesicherten Zugriff auf gemeinsame
Klar
Post by Alfred Gemsa
Solche Zugriffe müssen über CriticalSections oder mit Semaphoren
gegeneinander verriegelt werden, wie du schon richtig vermutest.
Ich habe schon ähnliche Sachen auf nem Microcontroller mit der UART
gemacht. Byte auslesen, in Ringbuffer speichern, Zeiger auf den
Ringbuffer incrementieren. Im Hauptprogramm testen, ob neue Daten da
sind und verarbeiten.
...
Danke für den Hinweis. Wir werden das morgen mal prüfen.

Gruß

Stefan
Heiko Nocon
vor 13 Jahren
Permalink
Post by Alfred Gemsa
Das hat aber bis dahin nix mit dem gesicherten Zugriff auf gemeinsame
Doch, natürlich hat es das. Wenn auf die zu schützende Datenstruktur
durch Synchronize ausschließlich im Kontext eines _einzigen_ Threads
(also des Mainthreads) zugegriffen wird, ist der Zugriff genauso gut
gesichert, als würde man CriticalSections verwenden, denn beides hat
eine Serialsisierung der Zugriffe zur Folge.

Die Entscheidung, welcher Sicherungsmechanismus im konkreten Fall besser
geeignet ist, hat also nichts mit der Güte der Sicherung zu tun, sondern
nur mit der Effizenz der Gesamtlösung.

Wenn der Mainthread jedes Zwischenergebnis der Satellitenthreads in
irgendeiner Form behandeln muß, dann ist mit Sicherheit Synchronize die
beste Methode zu Synchronisierung, denn jeder andere Mechanismus müßte
_zusätzlich_ zu Synchronize verwendet werden, was neben geringerer
Effizienz zur Folge hätte, daß die Gefahr für Deadlocks überhaupt erst
entsteht, denn jeder Thread muß statt mit einem nun mit zwei
Sync-"Objekten" hantieren.

Sind hingegen mehrere Satellitenthreads nur untereinander kooperativ und
erst das Endergebnis dieser Zusammenarbeit interessiert den Mainthread,
dann ist Synchronize schlecht, weil ineffizient.
--
Wer Komponenten ohne Quelltext oder richtig miese Komponenten
oder gute Komponenten mit Quelltext, ohne die Source zu verstehen, sich verschafft,
um sie in Form "eigener" Programme in Verkehr zu bringen,
der wird mit Gefängnis nicht unter 5 Jahren bestraft.
Sven Lanoster
vor 13 Jahren
Permalink
...
Diese public-Variablen sind doch globale Variablen, oder nicht?
Ich würde mich nicht trauen, globale Strukturen, die nicht atomar les-
und schreibbar sind, ohne zusätzliche Schutzmaßnahmen zu verwenden. Und
welche Datentypen heute atomar bearbeitbar sind, ist mir ganz und gar
nicht klar. Vermutlich alles, was exakt in ein Register der CPU passt,
also Integer und Pointer. Beim Byte wäre ich mir schon nicht mehr
sicher. Beim AnsiString gehe ich davon aus, dass es im Zweifel knallt,
wenn der Thread just in dem Moment schreibt, nachdem der Mainthread die
Hälfte gelesen hat.

Oder kümmern sich aktuelle Delphi-Versionen automagisch darum, dass die
Public-Felder eines Thread-Abkömmlings geschützt sind?

MfG,
Sven.
--
10 SIN
20 GOTO HELL
Futurama
Heiko Nocon
vor 13 Jahren
Permalink
Post by Sven Lanoster
Diese public-Variablen sind doch globale Variablen, oder nicht?
Ich würde mich nicht trauen, globale Strukturen, die nicht atomar les-
und schreibbar sind, ohne zusätzliche Schutzmaßnahmen zu verwenden.
Und das ist auch korrekt.
Post by Sven Lanoster
Und
welche Datentypen heute atomar bearbeitbar sind, ist mir ganz und gar
nicht klar. Vermutlich alles, was exakt in ein Register der CPU passt,
also Integer und Pointer.
Nein. Die Registerbreite allein sichert keinesfalls atomare Operationen
zu. Weil das so ist, gibt es z.B. das Interlocked-API. Und selbst das
funktioniert bloß bei bestimmten Alignments zuverlässig. Im absoluten
Minimum gilt: Align=Registerbreite, manchmal muß es aber auch noch
größer sein.
Post by Sven Lanoster
Beim Byte wäre ich mir schon nicht mehr
sicher.
Das wiederum ist bei jeglichen x86 und -nachfolgern immer für eine
Operation atomar. Allein deshalb, weil es die kleinste adressierbare
Einheit ist, auf die überhaupt zugegriffen werden kann. Dieser
Sachverhalt _impliziert_, daß ein Zugriff darauf immer atomar sein muß.
Wer logisch denken kann, ist hier ganz klar im Vorteil...

Aber selbst das hilft noch nicht immer weiter, denn insbesondere für die
Synchronisation genügt es nicht, wenn _ein_ Zugriff atomar ist. Dazu
müssen _zwei_ Zugriffe atomar sein. Nämlich ein Lesezugriff und ein
nachfolgender Schreibzugriff. Tatsächlich gibt es deshalb spezielle
Befehle im Befehlssatz, für die das durch die Hardware (mit viel Aufwand
in der Logik) sichergestellt wird. Die ganzen Synchronisationsobjekte
der Betriebssysteme bauen letztlich auf diese ganz wenigen wirklich für
diesen Zweck geeigneten Instruktionen auf.

Weil das so ist, ist heute jeder Versuch, in einer Hochsprache eigene
zuverlässige Synchronsationsmechanismen zu implementieren, zum Scheitern
verurteilt. Denn hier hat man nicht die Kontrolle darüber, welche
Instruktionen der Compiler wählt.

Wer klug ist, setzt deshalb _immer_ die Synchronisationsmechanismen ein,
die das OS bietet.
--
Wer Komponenten ohne Quelltext oder richtig miese Komponenten
oder gute Komponenten mit Quelltext, ohne die Source zu verstehen, sich verschafft,
um sie in Form "eigener" Programme in Verkehr zu bringen,
der wird mit Gefängnis nicht unter 5 Jahren bestraft.
Alfred Gemsa
vor 13 Jahren
Permalink
Post by Stefan
erstmal vorab, ich selber programmiere kaum noch in Delphi. Mein
Schwerpunkt ist Mikrocontrollerprogrammierung in Assembler und C
(WinAVR/GCC). Wir kämpfen aber aktuell mit einem Programm eines
ehemaligen Kollegen. Problem ist, das Programm hängt sich in
unregelmäßigen Abständen auf. Das können einige Stunden sein, oder auch
mal einige Wochen. Wir haben dort die diversen Threads, die insbesondere
für TCP/IP Kommunikation verwendet werden im Verdacht.
Falls du doch in Delphi tiefer einsteigen willst:

http://michael-puff.de/Programmierung/Delphi/Tutorials/Threads_mit_Delphi.pdf

Alfred.
Loading...