Auf Synchronize warten

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
MitjaStachowiak
Lazarusforum e. V.
Beiträge: 394
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Auf Synchronize warten

Beitrag von MitjaStachowiak »

Hallo,

ich habe gerade 'ne Miese Internetverbindung und kann nicht richtig recherchieren.

Wie schaffe ich es, an bestimmten Stellen auf einen TThread zu warten? Schematisch folgender Code funktioniert nicht:

Code: Alles auswählen

 
// Mainthread
var ready : Boolean = false;
startSecondThread;
while (not ready) do sleep(10);
 
procedure onEvent;
begin
 if (lastEvent = 'ready') then ready := true;
end;
 
// Second Thread
... do something...
lastEvent := 'Ein bestimmter Status';
Synchronize(@onEvent);
... do something...
lastEvent := 'ready';
Synchronize(@onEvent);
 


Solange sich der Main-Thread in der sleep-Schleife befindet, funktioniert das Synchronize nicht. Eventuell, wenn ich in der Schleife noch Application.ProcessMessages aufrufe, aber das könnte zu unerwünschten Nebenwirkungen führen :roll:

Synchronize einfach nicht benutzen und ready := true; direkt in den zweiten Thread zu schreiben ist keine Option, da in 99% der Fälle die Asynchronität über das Synchronize erwünscht ist. Andernfalls müsste ich im Hauptthread ständig Polling betreiben, um eventuelle Statusmeldungen zu bekommen.

Bevor man in die wait-Schleife geht eine Art dontSynchronizeFlag auf true setzen, funktioniert auch nicht zuverlässig.

Was ist die gängige Lösung, wenn man asynchron mit Synchronize arbeiten möchte, aber manchmal auf den Thread warten muss?

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Auf Synchronize warten

Beitrag von Scotty »

Waitfor ist eine Methode von TThread (http://www.freepascal.org/docs-html/cur ... itfor.html). Beispiele gibt es unter Lazarus\Examples\multithreading

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 394
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Auf Synchronize warten

Beitrag von MitjaStachowiak »

Nur warte ich nicht auf Terminate, sondern auf ein bestimmtes Event...

Es sieht so aus, als blieben da nur Windows-Messages.

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: Auf Synchronize warten

Beitrag von Socke »

MitjaStachowiak hat geschrieben:Nur warte ich nicht auf Terminate, sondern auf ein bestimmtes Event...

Es sieht so aus, als blieben da nur Windows-Messages.

Bei Ereignissen solltest du auch diese verwenden und nicht Synchronize zweckentfremden. http://www.freepascal.org/docs-html/cur ... reate.html

Im Allgmeinen halte ich es aber für einen besseren Stil, im GUI-Thread auf normale Event-Methoden zu setzen anstatt auf Threads zu warten (Stichwort: Programm bleibt "hängen").
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 394
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Auf Synchronize warten

Beitrag von MitjaStachowiak »

Ja, in 99% der Fälle ist es auch Eventbasiert, aber manchmal wird eine bestimmte Anfrage gesendet und bis zur Antwort darf sonst nichts passieren. Das war hier die Schwierigkeit - rein Eventbasiert müsste man wegen den paar Ausnahmen einen gigantischen Zustandsautomat einprogrammieren :|

Ich habe jetzt das folgende Objekt erstellt:

Code: Alles auswählen

 
type
  TDualQueue = object
    type
     TQueueItem = record
      sync    : Boolean;
      err     : Cardinal;
      command : AnsiString;
     end;
    strict private
     queue : Array of TQueueItem;
     queueLocked : Array [0..1] of Boolean;
    public
     constructor init;
     procedure enqueue(thread : Byte; v : TQueueItem);
     function dequeue(thread : Byte; var v : TQueueItem) : Boolean;
   end;   
 
implementation
 
constructor TDualQueue.init;
begin
 queue := nil;
 queueLocked[0] := false;
 queueLocked[1] := false;
end;
 
procedure TDualQueue.enqueue(thread : Byte; v : TQueueItem);
var i : integer;
label wait;
begin
 wait:
 queueLocked[thread] := true;
 if (queueLocked[thread xor 1]) then begin
  queueLocked[thread] := false;
  sleep(thread + 1);
  goto wait;
 end;
 i := Length(queue);
 SetLength(queue, i+1);
 for i := i downto 1 do queue[i] := queue[i-1];
 queue[0] := v;
 queueLocked[thread] := false;
end;
 
function TDualQueue.dequeue(thread : Byte; var v : TQueueItem) : Boolean;
var i : integer;
label wait;
begin
 wait:
 queueLocked[thread] := true;
 if (queueLocked[thread xor 1]) then begin
  queueLocked[thread] := false;
  sleep(thread + 1);
  goto wait;
 end;
 i := Length(queue)-1;
 if (i = -1) then Result := false
 else begin
  Result := true;
  v := queue[i];
  SetLength(queue, i);
 end;
 queueLocked[thread] := false;
end;                                                               
 


So können Programm und Thread gut kommunizieren, z.B. indem der MainThread immer enqueue(0, 'Befehl') macht und der WorkerThread immer if (not dequeue(1, befehl)) then // keine Befehle im queue!

Wenn auf eine bestimmte Antwort gewartet werden soll, kann man beim dequeue pollen und wenn der MainThread nicht wartet, dann posted der Thread eine Message-

Was noch doof ist, ist, dass ich immer die Unit Forms importieren muss, um über Application.MainForm.Handle ein brauchbares HWND zu bekommen. Kann ich ein Handle, das Windows-Messages empfangen kann, auch mit wenig Aufwand selber erzeugen?

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: Auf Synchronize warten

Beitrag von mschnell »

MitjaStachowiak hat geschrieben:Solange sich der Main-Thread in der sleep-Schleife befindet, funktioniert das Synchronize nicht.


Sleep ist keine Schleife, sondern die Mitteilung an das Betriebnssystem, dass andere Programme (oder Threads) laufen sollen.

Solange sich der Main-Thread im sleep befindet, funktioniert funktioniert eigentlich gar nichts. Sleep im Mainthread ist bei Ereignis-Programmierung (der "normalen" Methodik in Lazarus) verboten. Man darf nicht auf irgendetwas warten. Das, worauf man im Main Thread wartet, muss ein Ereignis auslösen (beim Thread z.B. durch "TThread.Synchronize" oder "TThread.Queue" oder Application.QueueAsyncCall) und der Mainthread wird dann (zu gegebener Zeit) dieses Ereignis bearbeiten.

Wenn man "warten" will, müssen die anderen Ereignisse, die durch die fehlende Fertig-Meldung blockiert werden, nicht ausgelöst werden oder notfalls die Fertigmeldung abfragen und sich dann unverrichteter Dinge beenden.

Die in "Synchronize" angegebene Ereignis-Funktion wird erst ausgeführt, wenn der Mainthread ein laufendes Ereignis verlassen hat. Und das wird durch sleep verhindert.

-Michael
Zuletzt geändert von mschnell am Di 1. Dez 2015, 10:02, insgesamt 2-mal geändert.

marcov
Beiträge: 1100
Registriert: Di 5. Aug 2008, 09:37
OS, Lazarus, FPC: Windows ,Linux,FreeBSD,Dos (L trunk FPC trunk)
CPU-Target: 32/64,PPC(+64), ARM
Wohnort: Eindhoven (Niederlande)

Re: Auf Synchronize warten

Beitrag von marcov »

Dafür kann man bestens TEvent verwenden. Der hat auch Waitfor, und kann man zb in einen anderen Thread auf signaled setzen

marcov
Beiträge: 1100
Registriert: Di 5. Aug 2008, 09:37
OS, Lazarus, FPC: Windows ,Linux,FreeBSD,Dos (L trunk FPC trunk)
CPU-Target: 32/64,PPC(+64), ARM
Wohnort: Eindhoven (Niederlande)

Re: Auf Synchronize warten

Beitrag von marcov »

MitjaStachowiak hat geschrieben:Ja, in 99% der Fälle ist es auch Eventbasiert, aber manchmal wird eine bestimmte Anfrage gesendet und bis zur Antwort darf sonst nichts passieren. Das war hier die Schwierigkeit - rein Eventbasiert müsste man wegen den paar Ausnahmen einen gigantischen Zustandsautomat einprogrammieren :|


Ist das überhaupt multiprocessor/core safe ohne locked Instructions ? (fences?)

Patito
Beiträge: 203
Registriert: Di 22. Sep 2009, 13:08
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit

Re: Auf Synchronize warten

Beitrag von Patito »

Ich denke mal dein Code funktioniert, aber aktives Pollen ist oft keine gute Lösung.
Die Idee mit den RTL-Events finde ich besser. (RTLeventCreate, RTLeventSetEvent, RTLeventWaitFor, ...)
TEvent ist manchmal auch noch eine Alternative. (Zumindest unter Windows hat es bei mir funktioniert).

Etwas irritierend ist nur, dass man beim RTLeventWaitFor mit Timeout keine Rückmeldung kriegt,
ob das Event gekommen ist, oder ob man in den Timeout gerannt ist.

Im Moment würde ich für Sachen, bei denen ich Timeouts benötige, eher zur rohen Windows-API greifen.

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 394
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Auf Synchronize warten

Beitrag von MitjaStachowiak »

Ist das überhaupt multiprocessor/core safe ohne locked Instructions ? (fences?)

Den TDualQueue habe ich extra für den Multi-Code betrieb geschrieben. So lange der Cache MESI befolgt (Weiß nicht, wie es bei einem echten Multiprozessorboard aussieht) müsste das stabil laufen.

Im Falle der seltenen Warteschleifen Polling zu betreiben halte ich schon für die richtige Lösung. Events kommen in der Sleep-Schleife ja nur an, wenn man Application.ProcessMessages aufruft, aber das lief nicht sehr stabil und es könnten dadurch die Ereignisroutinen der GUI aufgerufen werden, was den Programmablauf kaputt machen würde.

Patito
Beiträge: 203
Registriert: Di 22. Sep 2009, 13:08
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit

Re: Auf Synchronize warten

Beitrag von Patito »

MitjaStachowiak hat geschrieben:Im Falle der seltenen Warteschleifen Polling zu betreiben halte ich schon für die richtige Lösung. Events kommen in der Sleep-Schleife ja nur an, wenn man Application.ProcessMessages aufruft, aber das lief nicht sehr stabil und es könnten dadurch die Ereignisroutinen der GUI aufgerufen werden, was den Programmablauf kaputt machen würde.


Diese RTLEvents kapseln soweit ich sehe rohe Synchronisationsobjekte (pthread_mutex o.ä.).
Mit den GUI-Eventschleifen und Application.ProcessMessages haben die (hoffentlich) nichts zu tun.

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 394
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Auf Synchronize warten

Beitrag von MitjaStachowiak »

Gut zu wissen. Muss mich da mal einarbeiten.

TThread ist ja auch nur eine Möglichkeit, Multithreading zu betreiben. Gibt es noch andere Konzepte?

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: Auf Synchronize warten

Beitrag von Socke »

Patito hat geschrieben:Etwas irritierend ist nur, dass man beim RTLeventWaitFor mit Timeout keine Rückmeldung kriegt,
ob das Event gekommen ist, oder ob man in den Timeout gerannt ist.

Im Moment würde ich für Sachen, bei denen ich Timeouts benötige, eher zur rohen Windows-API greifen.

Du hast Recht; TEvent hingegen gibt dieses Feedback; ich kann mir nur nie merken, dass hierzu die Unit SyncObjects eingebunden werden muss.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

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

Re: Auf Synchronize warten

Beitrag von Warf »

Wie andere bereits schon erwähnt haben sollte der Mainthread auf gar keinen Fall auf andere Threads warten. Wenn du dich nun aus was für gründen auch immer dafür entscheidest dass du das doch machen willst könntest Semaphores bzw CriticalSections verwenden

Code: Alles auswählen

WaitSection: TRTLCriticalSection;
TMyThread.Create(...)
...
InitializeCriticalSection(WaitSection);
...
TMyThread.Execute...
 EnterCriticalSection(WaitSection);
try
 DoSomething;
 Synchronize(...);
finally
  LeaveCriticalSection(WaitSection);
end;
 
TMyThread.Destroy...
DeleteCriticalSection(WaitSection);
 
//MainThread
DoSomething;
//Wait for TMyThread to Synchronize
EnterCriticalSection(WaitSection);
try
...
finally
  LeaveCriticalSection(WaitSection);
end;
 


Sobald du im Mainthread EnterCriticalSection aufrufst wartet er automatisch bis zum LeaveCriticalSection des Threads (direkt nach dem Synchronize), ob du zischen Enter und LeaveCriticalSection irgend was machst bleibt dir überlassen, du kannst auch, wenn du nur warten willst den block dazwischen leer lassen

TThread ist ja auch nur eine Möglichkeit, Multithreading zu betreiben. Gibt es noch andere Konzepte?


Kinderprozesse, bzw unter Unix speziell fork:

Code: Alles auswählen

program ForkTest;
...
uses BaseUnix;
 
var i: Integer;
begin
  i:=fpFork;
  if LongBool(i) then
    WriteLn('VaterProzess')
  else
    WriteLn('KindsProzess');
end;


Threads und Prozesse haben andere Eigenschaften, weswegen sich ich verschiedenen Situationen mal das eine, mal das andere mehr lohnt.

Z.B. hat ein eigener Prozess eigene CPU Zeit, während sich normalerweise alle Threads die Prozess CPU Zeit teile => Mehr Leistung, aber unfair gegenüber anderen Prozessen
Dafür haben Threads einen geteilten Adressbereich, Prozesse benötigen IPC zum kommunizieren.

Um mal 2 Eigenschaften zu nennen

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 394
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Auf Synchronize warten

Beitrag von MitjaStachowiak »

Stimmt, CriticalSection... Das hatte ich schon vergessen.

Antworten