Datenübergabe an TThread richtig gemacht ?

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Datenübergabe an TThread richtig gemacht ?

Beitrag von corpsman »

Servus allerseits,

ich habe mir da eine kleine "Lösung" für mein Problem gebastelt und wollte mal eure Meinung hören.

Worum es geht :

Ich habe ein TThread bei dem Rufe ich eine Routine vom Hauptthread aus auf, welche den Thread mit Arbeit versorgt.
Die Routine soll ein Ergebniss berechnen, dieses kann entweder berechnet werden, dann kommt es zurück, oder es dauert länger als 10ms dann kommt der Fehlercode.

Und hier ist meine Implemenetierung:

Code: Alles auswählen

 
// Der Teil der die Arbeit macht
Procedure TTest.Execute;
Var
  wi: integer;
Begin
  While Not Terminated Do Begin
    If fNeedWorkToDo Then Begin
      fIsWorking := true;
      wi := fWorkIndex;
      If random(100) < 10 Then Begin  // in 10 % aller Fälle können wir das Ergebnis nicht verarbeiten und brauchen ewig zum Auflösen..
        sleep(200);
      End;
      fWorkResult := inttostr(wi);
      fNeedWorkToDo := false;
      fIsWorking := false;
    End;
    sleep(1);
  End;
End;
 
// Der Teil der die Arbeit aus dem Hauptthread entgegen nimmt und weiterreicht
Function TTest.DoWork(Index: integer): String;
Var
  i: int64;
Begin
  If fIsWorking Then Begin // So lange der Thread noch Arbeitet kann nichts bearbeitet werden also Raus
    result := '?';
    exit;
  End;
  fWorkIndex := index;
  fWorkResult := '?';
  fNeedWorkToDo := true; // Dem Thread Execute das Startsignal geben
  (*
   * Wir warten maximal 10ms, dann brechen wir ab.
   * ist Der Thread vorher Fertig setzt er sein fNeedWorkToDo auf False und wir können das Ergebnis
   * übernehmen
   *)

  i := GetTickCount64;
  While (GetTickCount64 - i <= 10) And fNeedWorkToDo Do Begin
 
  End;
  result := fWorkResult;
End;


Unter der Annahme dass das schreiben eines Boolean Wertes Atomar ist, habe ich 2 Kontrollflussvariablen "fIsWorking" und "fNeedWorkToDo" die sicherstellen, dass ich keine ReadModifyWrite Probleme bekomme.
Wenn man das Testprogramm laufen läst scheint alles zu funktionieren. Aber ist diese Implementierung OK / Erlaubt / ThreadSave / .. ?
Dateianhänge
Thread_Handling.zip
Vollständiger Code zum Testen
(3.09 KiB) 71-mal heruntergeladen
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von mse »

corpsman hat geschrieben:Unter der Annahme dass das schreiben eines Boolean Wertes Atomar ist,

Darauf kann man sich AFAIK bei multicore-Prozessoren nicht mehr verlassen. Besser ist die "Interlocked" Operationen aus rtl/inc/systemh.inc oder mutex/critical section zu verwenden.

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von corpsman »

Das ich es nicht Optimal gemacht habe dachte ich mir schon, deswegen der Post.

Was ist ein interlocked, ?

Critical Sections kenne ich, muss mal schaun wie man das dahin gegehend umbauen könnte..
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von mse »

corpsman hat geschrieben:Was ist ein interlocked, ?

Das sind diese Funktionen:

Code: Alles auswählen

 
function InterLockedIncrement (var Target: longint) : longint; public name 'FPC_INTERLOCKEDINCREMENT';
function InterLockedDecrement (var Target: longint) : longint; public name 'FPC_INTERLOCKEDDECREMENT';
function InterLockedExchange (var Target: longint;Source : longint) : longint; public name 'FPC_INTERLOCKEDEXCHANGE';
function InterLockedExchangeAdd (var Target: longint;Source : longint) : longint; public name 'FPC_INTERLOCKEDEXCHANGEADD';
function InterlockedCompareExchange(var Target: longint; NewValue: longint; Comperand: longint): longint; public name 'FPC_INTERLOCKEDCOMPAREEXCHANGE';
{$ifdef cpu64}
function InterLockedIncrement64 (var Target: int64) : int64; public name 'FPC_INTERLOCKEDINCREMENT64';
function InterLockedDecrement64 (var Target: int64) : int64; public name 'FPC_INTERLOCKEDDECREMENT64';
function InterLockedExchange64 (var Target: int64;Source : int64) : int64; public name 'FPC_INTERLOCKEDEXCHANGE64';
function InterLockedExchangeAdd64 (var Target: int64;Source : int64) : int64; public name 'FPC_INTERLOCKEDEXCHANGEADD64';
function InterlockedCompareExchange64(var Target: int64; NewValue: int64; Comperand: int64): int64; public name 'FPC_INTERLOCKEDCOMPAREEXCHANGE64';
{$endif cpu64}
{ Pointer overloads }
{$ifdef cpu64}
function InterLockedIncrement (var Target: Pointer) : Pointer; external name 'FPC_INTERLOCKEDINCREMENT64';
function InterLockedDecrement (var Target: Pointer) : Pointer; external name 'FPC_INTERLOCKEDDECREMENT64';
function InterLockedExchange (var Target: Pointer;Source : Pointer) : Pointer; external name 'FPC_INTERLOCKEDEXCHANGE64';
function InterLockedExchangeAdd (var Target: Pointer;Source : Pointer) : Pointer; external name 'FPC_INTERLOCKEDEXCHANGEADD64';
function InterlockedCompareExchange(var Target: Pointer; NewValue: Pointer; Comperand: Pointer): Pointer; external name 'FPC_INTERLOCKEDCOMPAREEXCHANGE64';
{$else cpu64}
function InterLockedIncrement (var Target: Pointer) : Pointer; external name 'FPC_INTERLOCKEDINCREMENT';
function InterLockedDecrement (var Target: Pointer) : Pointer; external name 'FPC_INTERLOCKEDDECREMENT';
function InterLockedExchange (var Target: Pointer;Source : Pointer) : Pointer; external name 'FPC_INTERLOCKEDEXCHANGE';
function InterLockedExchangeAdd (var Target: Pointer;Source : Pointer) : Pointer; external name 'FPC_INTERLOCKEDEXCHANGEADD';
function InterlockedCompareExchange(var Target: Pointer; NewValue: Pointer; Comperand: Pointer): Pointer; external name 'FPC_INTERLOCKEDCOMPAREEXCHANGE';
{$endif cpu64}
{ unsigned overloads }
function InterLockedIncrement (var Target: cardinal) : cardinal; external name 'FPC_INTERLOCKEDINCREMENT';
function InterLockedDecrement (var Target: cardinal) : cardinal; external name 'FPC_INTERLOCKEDDECREMENT';
function InterLockedExchange (var Target: cardinal;Source : cardinal) : cardinal; external name 'FPC_INTERLOCKEDEXCHANGE';
function InterLockedExchangeAdd (var Target: cardinal;Source : cardinal) : cardinal; external name 'FPC_INTERLOCKEDEXCHANGEADD';
function InterlockedCompareExchange(var Target: cardinal; NewValue: cardinal; Comperand: cardinal): cardinal; external name 'FPC_INTERLOCKEDCOMPAREEXCHANGE';
{$ifdef cpu64}
function InterLockedIncrement64 (var Target: qword) : qword; external name 'FPC_INTERLOCKEDINCREMENT64';
function InterLockedDecrement64 (var Target: qword) : qword; external name 'FPC_INTERLOCKEDDECREMENT64';
function InterLockedExchange64 (var Target: qword;Source : qword) : qword; external name 'FPC_INTERLOCKEDEXCHANGE64';
function InterLockedExchangeAdd64 (var Target: qword;Source : qword) : qword; external name 'FPC_INTERLOCKEDEXCHANGEADD64';
function InterlockedCompareExchange64(var Target: qword; NewValue: qword; Comperand: qword): int64; external name 'FPC_INTERLOCKEDCOMPAREEXCHANGE64';
{$endif cpu64}
 

welche "garantiert" atomisch sind.

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von corpsman »

Hmm, für Boolean gibt es keine Interlocked routinen, oder bin ich Blind ?

Wenn ich deine Argumente von vorher richtig verstehe, sollte ich meinen Boolean, dann als Integer und interlocked umsetzen ?
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von mse »

Ja. Diese Funktionen benutzen nach Möglichkeit Mechanismen des Prozessors, deshalb kein boolean. Es ist aber kein Problem Integerwerte von 0 als false und alles andere als true zu interpretieren.

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: Datenübergabe an TThread richtig gemacht ?

Beitrag von mschnell »

Zu beachten ist, dass die Interlocked Instruktionen durch die Notwendigkeit, die Caches der Prozessoren zu synchronisieren ganz wesentlich (u.u. Tausendfach) langsamer sind als alle anderen Instruktionen. Manchmal lassen sie sich aber nicht vermeiden.

-Michael

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von corpsman »

Dennoch müsste ich den obigen Mechanismus mittels interlocked machen, auch wenn ich das nu 3h laufen lassen hab (100 Aufrufe Pro Sekunde mit 1% Fehler => dauer > timeout) und nie ein Fehler auftrat ?
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von mse »

Ja.
Es sei denn du kannst mit dem Risiko leben, dass das Atomkraftwerk irgendwann explodiert, das Flugzeug abstürzt, die Rakete ihr Ziel nicht erreicht, ein Krieg ausgelöst wird...
Viele Gesellschaften können das.
Du musst Michaels Einwurf nicht so ernst nehmen, wenn die Synchronisationsfunktion nicht allzu häufig ausgeführt wird. Und wenn sie sehr häufig ausgeführt wird, solltest du einen anderen Synchronisiermechanismus ins Auge fassen (Semaphore, Mutex, Condition, Eventqueue...).

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: Datenübergabe an TThread richtig gemacht ?

Beitrag von mschnell »

mse hat geschrieben:Und wenn sie sehr häufig ausgeführt wird, solltest du einen anderen Synchronisiermechanismus ins Auge fassen (Semaphore, Mutex, Condition, Eventqueue...).

???? Die genannten Mechanismen basieren im Endeffekt auch auch Interlocked instructions (tief im Betriebssysetem vergraben). Das ist auf jeden Fall wesentlich langsamer als diese Instructions direkt zu verwenden. Also gerade bei häufigen Aufrufen macht es Sinn das "von Hand" zu machen (auch wenn das mehr Denkarbeit bei der Planung verlangt). Es ist nur eben wesentlich langsamer als ohne Interlock, was aber nicht heißt, dass man es untgerlassen könnte.

-Michael

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Datenübergabe an TThread richtig gemacht ?

Beitrag von mse »

Nehmen wir MSEgui-Funktionen als Anschauungsobjekte. Im ersten Thread warten wir auf den Zweiten durch Aufruf von sys_semwait(). Im Zweiten melden wir Bereitschaft durch Aufruf von sys_sempost(). Der erste Thread braucht während der Wartezeit keine Rechenleistung.
Wird im Gegensatz dazu im ersten Thread ein entsprechedes Flag z.B durch InterlockedExchange() in einer Schlaufe überwacht, brauchen beide Threads Rechenleistung.
MSEgui hat übrigens für solche Zwecke TSemThread mit den Funktionen:

Code: Alles auswählen

 
   function semwait(const atimeoutus: integer = 0): boolean;
   function sempost: boolean; //true if not destroyed
   function semtrywait: boolean;
   function semcount: integer;
 

TSemThread erbt von TMutexThread mit den Funktionen:

Code: Alles auswählen

 
   function lock: boolean; //true if ok
   procedure unlock;
 

Von TSemThread erbt TEventThread mit den Funktionen:

Code: Alles auswählen

 
   procedure postevent(event: tmseevent);
   procedure clearevents;
   function waitevent(const timeoutus: integer = -1): tmseevent;
                 // -1 infinite, 0 no block
   function eventcount: integer;
 

Dann gibt es noch die TThreadComp Komponente, welche einen TEventThread kapselt und die Ereignisse als Ereignis-Eigenschaften im Objektinspektor zugänglich macht. Die Synchronisierung mit dem Mainthread durch Application.Lock()/Unlock() oder Application.Synchronize() und die Vermeidung von Deadlocks beim Abräumen geschieht automatisch.

Aber vielleicht meinst du Lock-freie Algorithmen?

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: Datenübergabe an TThread richtig gemacht ?

Beitrag von mschnell »

mse hat geschrieben:Aber vielleicht meinst du Lock-freie Algorithmen?
Sonst hat es wenig Zweck die Synchronisation selbst in die Hand zu nehmen.

Simples Beispiel: eine Funktion wird von meheren Threads aufgerufen und man möchte einfach zählen, wie oft die Funktion aufgerufen wurde.

Wenn komplexere Sachen "Atomic" zu machen sind, verwendet man natürlich die von der RTL zur Verfügung gestellten Mittel.

Für locking ist (bei Linux) am effektivsten ein Futex. ("Fast User Space Mutex"). Bei C-Programmen braucht sich der User darum nicht zu kümmern, da die allgemeine PTHreadLib bei "PThreadMutex" automatisch FUTEX für die Architekturen verwendet, bei denen das verfügbar ist (z.B. alle X86) und normalen (im Durchsnitt viel langsameren) Betriebssystem-Mutex, falls FUTEX (noch) nicht existiert.

Bei FPC / mse und Windows weiß ich nicht, ob und wie Futex einsetzbar ist

-Michael

Antworten