wie shared man daten zwischen main und subthreads? [gelöst]

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

wie shared man daten zwischen main und subthreads? [gelöst]

Beitrag von laz847 »

Hallo zusammen, ich habe zwar schon eine ähnliche Frage offen, bei diesem Thema hier gehts aber nicht um eine konkrete Frage zu meinem Code sondern eher darum, wie Ihr es macht, bzw. machen würdet.

Nehmen wir an, wir haben in unseren Programm den MainThread (MT) und 7 WorkerThreads (WT).

In unserem Programm sollen Daten in einem Chart dargestellt werden, jeder WT soll die Berechnungen für einen Tag machen, das Hauptprogramm soll die Daten für 7 Tage dann - wenn die WT's die Berechnungen erledigt haben - anzeigen.

Jetzt muss ich die Daten für die Chartanzeige ja irgendwie ins Hauptprogramm bekommen, denn im Chart zeichnen dürfen meine WT's nicht direkt (wenn dann nur über Syncronize, was ja der MT dann erledigt).

Ich möchte aber nicht, dass jeder Thread einzeln syncronisiert zeichnet - weil dann alle anderen immer warten müssen bis sie dran sind.

Ich möchte das die Threads das machen was sie am Besten können, rechnen und dann die Ergebnisse dem MT zur weiteren Verarbeitung / Anzeige übergeben.

Bisher verwende ich dafür ein globales 2d Array, siehe : http://www.lazarusforum.de/viewtopic.php?f=10&t=8639

Callbacks und Messages klammer ich hier mal aus, da es darum geht, mehrere bzw. umfangreiche Datensätze zu übertragen.

Danke für Eure Vorschläge! :)
Zuletzt geändert von laz847 am Di 7. Apr 2015, 16:36, insgesamt 3-mal geändert.

Benutzeravatar
theo
Beiträge: 10859
Registriert: Mo 11. Sep 2006, 19:01

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von theo »

Mal ne andere Frage: Bist du sicher, dass hier Multithreading sinnvoll ist?
Wäre es nicht u.U. sinnvoller zu versuchen, das Rechnen und Zeichnen zu beschleunigen?

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von laz847 »

Hallo Theo,

naja in diesem BEISPIEL könnte man wahrscheinlich auch auf Multithreading verzichten, je nach Anforderung bzw. nachdem wieviel Rechenlast da anfällt. Das ist jedoch nur ein Beispiel, ein Container um meine Frage darzustellen. Es geht hier nicht um die Frage ob Multithreading in diesem Beispiel sinnvoll/nötig ist, es geht um die Frage : "Wie shared man korrekt Daten zwischen Main und Subthreads?" :D.

Bitte nicht falsch verstehen, ich beantworte Deine Frage gern trotzdem. In meinem Fall nutze ich Threads um (a) die Rechenlast auf 4 Kerne zu verteilen und (b) wegen der Gleichzeitigkeit. Wenn ich 7 x 100000 Datensätze durchziehen muss ist das mit mehreren Kernen einfach schneller oder nicht? Ausserdem soll man ja im Hauptprogramm ohne Ruckler im Chart arbeiten können (Zoomen, Chart verschieben, usw...), von daher bietet es sich an die Berechnungen -soweit es geht - aus dem Mainthread zu verlagern.

Benutzeravatar
theo
Beiträge: 10859
Registriert: Mo 11. Sep 2006, 19:01

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von theo »

Na gut, aber so allgemein kann ich dazu nicht viel sagen.
Wenn ein Tread im Mainthread zeichnen will, muss das via "Synchronize" geschehen, da sehe ich keine Alternative.
Du kannst natürlich in jedem Thread auf ein fpImage zeichnen und dieses dann nur noch im Mainthread auf den Screen kopieren.

Aber wie gesagt, das hängt von den Details ab. So tief will ich da auch nicht eindringen.

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von Michl »

Wie im anderen Thema beantwortet, kannst du von jedem Thread mit Synchronize Daten zum Mainthread übergeben. Von Thread zu Thread eignen sich CriticalSections.

Anbei die zwei Beispiele (Package TAChartLazarusPkg wird benötigt). Ich hoffe, ich habe auf die Schnelle nichts übersehen (würde mich auch mal interessieren, ob diese problemlos auf Linux laufen?!).
Dateianhänge
CharAndThreadCS.zip
(4.48 KiB) 171-mal heruntergeladen
ChartAndThreadSynchronize.zip
(4.43 KiB) 162-mal heruntergeladen

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von laz847 »

Lieber Theo, entweder stell ich meine Fragen falsch oder Du liest etwas zu fix :(
Wenn ein Tread im Mainthread zeichnen will, muss das via "Synchronize" geschehen, da sehe ich keine Alternative.
Ich möchte aber nicht, dass jeder Thread einzeln syncronisiert zeichnet - weil dann alle anderen immer warten müssen bis sie dran sind. Ich möchte das die Threads das machen was sie am Besten können, rechnen und dann die Ergebnisse dem MT zur weiteren Verarbeitung / Anzeige übergeben.
Wie im anderen Thema beantwortet, kannst du von jedem Thread mit Synchronize Daten zum Mainthread übergeben. Von Thread zu Thread eignen sich CriticalSections.
Sorry aber da geht jetzt irgendwie auch etwas durcheinander, ich brauche / möchte doch keinen Synchronize() um DATEN aus dem WT in den MT zu übergeben. Im anderen Thema verwende ich ein globales Array nennen wir es (wie dort) einfach mal globA, die erste Ebene enthält hier für dieses Beispiel jetzt mal nur 7 Plätze. Von mir aus machen wir die erste Ebene jetzt auch statisch, macht ja keinen Unterschied.

Code: Alles auswählen

var
globA : array[0..6] of array of record; // jeder der 7 threads hat seinen platz
 
procedure TEvoThread.Execute;
var 
 tmp : array of record;
 
begin
 
 while(w1) do begin
 
  tmp := nil;
 
  while(w2) do begin 
   [.......] //  tmp[] wird resized und neu befüllt
  end; 
 
  globA[tid] := tmp; // tid ist thread id 0,1,2,3,4,5,6 > tmp wird in die 2 ebene kopiert
 
 end;
 
end;
Wenn alle Threads fertig sind kann nun der MainThread die Daten in den Chart einzeichnen, diese sind nun in globA[0,1,2,3,4,5,6] verfügbar.

Die Rechenlast liegt in den WorkerThreads, das Zeichnen im Chart macht der MainThread.

Das war / ist mein Weg wie ich Daten aus dem WT in den MT übergebe, ich wollte jetzt wissen was es da noch so gibt bzw. was da besser wäre oder oder oder...

Hier sollte weder ein Syncronize noch eine CriticalSection nötig sein?
Zuletzt geändert von laz847 am Mo 30. Mär 2015, 22:40, insgesamt 3-mal geändert.

wp_xyz
Beiträge: 5130
Registriert: Fr 8. Apr 2011, 09:01

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von wp_xyz »

Michl hat geschrieben:würde mich auch mal interessieren, ob diese problemlos auf Linux laufen?!
Nö - Unter Windows laufen beide Demos problemlos, aber unter Mint hängt jedes Programm nach den Setzen des ersten Häkchens.

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von Michl »

wp_xyz hat geschrieben:Nö - Unter Windows laufen beide Demos problemlos, aber unter Mint hängt jedes Programm nach den Setzen des ersten Häkchens.
Interessant. Ich muss mir doch mal ein Linux in der VM installieren... :cry:
laz847 hat geschrieben:Hier sollte weder ein Syncronize noch eine CriticalSection nötig sein?
K.A., ich hatte mal ein ähnliches Problem und dachte meine Daten sind "threadsicher" und hatte auch Gelegenheitsabstürze (irgendwo hier im Forum ist auch noch der Thread dazu). Abhilfe hatte ein konsequentes Einsetzen von Criticalsections gebracht.

Interessante Lektüre zum Thema: http://free-pascal-general.1045716.n5.n ... l#a5719514

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Benutzeravatar
theo
Beiträge: 10859
Registriert: Mo 11. Sep 2006, 19:01

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von theo »

Michl hat geschrieben:
wp_xyz hat geschrieben:Nö - Unter Windows laufen beide Demos problemlos, aber unter Mint hängt jedes Programm nach den Setzen des ersten Häkchens.
Interessant. Ich muss mir doch mal ein Linux in der VM installieren... :cry:
Nö, der dürfte klar sein.
Im lpr steht ja immer das da:

Code: Alles auswählen

 
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}   
UseCThreads ist aber meistens nicht definiert. Ich lösche das {$IFDEF UseCThreads} immer da raus {$IFDEF Unix} reicht.

Benutzeravatar
theo
Beiträge: 10859
Registriert: Mo 11. Sep 2006, 19:01

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von theo »

laz847 hat geschrieben:Hier sollte weder ein Syncronize noch eine CriticalSection nötig sein?
Seh ich wie Michl. Dafür würde ich nicht die Hand ins Feuer legen.

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von laz847 »

Bin gerade dabei mir Deine Beispiele anzusehen, erstmal dickes Danke dafür, dass Du dir soviel Mühe machst!

In CharAndThreadCS ist genau was ich meine, aber Du hast noch eine CriticalSection drum. Die fehlt bei mir und laut dem was ich bisher von Euch erfahren habe, muss da keine drum.

Dein Timer liest unkoordiniert, man weiß nie wann, ich lese jedoch NUR nachdem die Threads beendet wurden.

Um das klar zu stellen, ich nutze die Criticalsections wenns denn sein muss, ich möchte aber bis ins Details verstehen warum und wann.

Edit : Edit wegen Blödsinn ;) In den Beispielen muss syncronisiert oder mit CriticalSections gearbeitet werden da der Timer unkontrolliert liest.
Zuletzt geändert von laz847 am Mo 30. Mär 2015, 23:11, insgesamt 3-mal geändert.

wp_xyz
Beiträge: 5130
Registriert: Fr 8. Apr 2011, 09:01

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von wp_xyz »

theo hat geschrieben:
Michl hat geschrieben:
wp_xyz hat geschrieben:Nö - Unter Windows laufen beide Demos problemlos, aber unter Mint hängt jedes Programm nach den Setzen des ersten Häkchens.
Interessant. Ich muss mir doch mal ein Linux in der VM installieren... :cry:
Ich lösche das {$IFDEF UseCThreads} immer da raus {$IFDEF Unix} reicht.
Natürlich. Dann geht's.

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von laz847 »

Also bei mir laufen beide Beispiele wunderbar und ich hab mir gleich noch jede Menge abgeschaut.

Aktuell starte ich meine Threads, rechne, dann sind die Threads beendet.

Nun zeichnet der MT die Daten in den Chart, dann starte ich erneut die Threads usw...

Deswegen gehts (vermutlich) bei mir auch ohne Syncronisation, aber das ständige Neustarten + Beenden macht keinen Sinn.

Laufen die natürlich durchgängig, gehts nicht ohne Syncronise / CriticalSections.

Nun wird mir auch klar, dass und wo mein grundsätzliches Modell von der Norm abweicht, weswegen ich dauernd alles Mögliche falsch verstanden hab.

Noch dazu ich mache ich mit meiner Version 1000000000 mal ein Create und Free, Du 1 x.

Für Dich ein paar Zeilen Code, für mich Goldstaub :D :D :D !

Und mir wurde dann auch gleich noch klar wozu Terminate benutzt wird, brauchte ich ja bisher nicht.

Nochmals DANKE! Du hast gleich mal 3, 4, 5 Fragen in einem Rutsch beantwortet :D

Da kommt jetzt nen bissl Arbeit auf mich zu :mrgreen:, danke das ich von Euch lernen durfte!

Aber eine Frage noch :D

Code: Alles auswählen

procedure TForm1.CheckGroup1ItemClick(Sender: TObject;
  Index: integer);
begin
  Inc(Index);
  if Assigned(Threads[Index]) then begin
    Threads[Index].Terminate;
    Threads[Index]:=Nil; // ist das hier sofort nicht gefährlich?
  end
  else
    Threads[Index]:=TMyThread.Create(Index);
end; 
Kann man da sofort nil setzen?

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von Michl »

theo hat geschrieben:UseCThreads ist aber meistens nicht definiert. Ich lösche das {$IFDEF UseCThreads} immer da raus {$IFDEF Unix} reicht.
:mrgreen: Ein Hast-du-kein-Linux-zum-testen-dann-hast-du-kein-Linux-getestet-Fehler... Danke!


@laz847: Es war ja nur eine Antwort auf die o.g. Frage, nicht der Versuch dein Problem dir zu lösen.

Mehr fällt mir zum derzeitigen Informationsstand allerdings auch nicht ein. Was ich dir aber noch mitgeben möchte ist, dass wenn immer ich ein Bug habe (und kann ihn nicht lokalisieren), versuche ich diesen mit Reduktion aufzuspüren. D.h. ich erstelle eine Kopie vom Projekt und schmeiße die Teile raus, die mir stabil scheinen, bis irgendwann der Fehler übrigbleibt.

Die Frage, ob das Setzen von Längen in einem globalen Array per Thread möglich ist, hätte ich mir in einem Test versucht zu beantworten (d.h. ich habe es tatsächlich einfach mal in Code gefasst - bei mir gibts keine Probleme - wenn du wirklich keine konkurierenden Zugriffe auf das jeweilige Array hast, könnte dir das vermutlich keine Probleme bereiten):

Code: Alles auswählen

type
 
  { TMyThread }
 
  TMyThread = Class(TThread)
    public
      constructor create(Nr: Integer);
    protected
      procedure Execute; override;
    private
      FNr: Integer;
  end;
 
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    MyThread: Array[0..99] of TMyThread;
  end;
 
var
  Form1: TForm1;
  MyArray: Array of Array of Integer;
 
implementation
 
{$R *.lfm}
 
{ TMyThread }
 
constructor TMyThread.create(Nr: Integer);
begin
  FNr:=Nr;
  inherited create(False);
end;
 
procedure TMyThread.Execute;
begin
  while not Terminated do
    SetLength(MyArray[FNr], Random(10000));
end;
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  SetLength(MyArray, 100);
  for i:=0 to 99 do MyThread[i]:=TMyThread.Create(i);
end; 

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: wie shared man korrekt daten zwischen main und subthread

Beitrag von laz847 »

Die Frage, ob das Setzen von Längen in einem globalen Array per Thread möglich ist, hätte ich mir in einem Test versucht zu beantworten
Hatte ich und es geht ja auch, nur war die andere Frage ob es Probleme machen kann und ob ich mit SetLength oder nil arbeiten muss während ich das tue. :)
Was ich dir aber noch mitgeben möchte ist, dass wenn immer ich ein Bug habe (und kann ihn nicht lokalisieren), versuche ich diesen mit Reduktion aufzuspüren. D.h. ich erstelle eine Kopie vom Projekt und schmeiße die Teile raus, die mir stabil scheinen, bis irgendwann der Fehler übrigbleibt.
Genau das mach ich gerade, ich hatte bereits alles bis auf die Stellen die ich jetzt hier im Forum angefragt habe ausgeschlossen.Wo ich mir unsicher war, hab ich nachgefragt. Dabei hat sich jetzt ein wichtiger Unterschied ergeben. Pro Durchlauf brauche ich ca. 100 ms, das heißt ich habe in der Schleife tagelang alle 100ms Millionen mal Threads created, freigegeben, wieder neu created, freigegeben usw...

Code: Alles auswählen

while(true) do begin
 
  for tid := 0 to tcp - 1 do begin 
   if(arr[tid] = nil) then arr[tid] := TEvoThread.Create(tid + 1); 
   if(tid = 0) then while(not(arr[0].thread_1_ready)) do Application.ProcessMessages; 
  end;
 
  for tid := 0 to tcp - 1 do if(Assigned(arr[tid])) and not(arr[tid].Terminated) then Sleep(1); 
  for tid := 0 to tcp - 1 do if(Assigned(arr[tid])) then FreeAndNil(arr[tid]);
 
  // globales Array globA lesen und darstellen
 
end;
Wenn hier mal nicht der Hase im Pfeffer liegt :D ?
Setzen von Längen in einem globalen Array per Thread möglich ist
Das wurde nur durch meinen komischen Aufbau möglich, blöd trifft dumm sozusagen :D...

Wenn Du magst, ich baue da gerade weiter dran rum, hab den Timer raus und das Zeichnen mit in die syncronisierte Funktion genommen. Alle Sleeps raus, ich will mal sehen wie sauber das unter Volllast läuft. 1 Thread läuft ruckelfrei, bereits ab 2 ruckelts, wie machen die das z.B. im Systemmonitor wo 4 gleichzeitig ohne (sichtbares) Ruckeln angezeigt werden?

Code: Alles auswählen

Synchronize(@ShowData);
 
procedure TMyThread.ShowData; // so wird NUR gezeichnet wenn neue daten anliegen
var
 
 LS: TLineSeries;
 
begin
 
 Daten[FNr] := FData;
 
 case FNr of
  1: LS := Form1.Chart1LineSeries1;
  2: LS := Form1.Chart1LineSeries2;
  3: LS := Form1.Chart1LineSeries3;
  4: LS := Form1.Chart1LineSeries4;
  5: LS := Form1.Chart1LineSeries5;
  6: LS := Form1.Chart1LineSeries6;
  7: LS := Form1.Chart1LineSeries7;
 end;
 
 LS.BeginUpdate; 
 LS.Clear; 
 LS.AddArray(Daten[FNr]); 
 LS.EndUpdate; 
 Form1.Repaint;
 
 Application.ProcessMessages; // hilft hier nicht
 
end;
Edit : Aha, weil ich das Sleep() entfernt habe ruckelts bei mir, das Sleep() im WT sorgt dafür das die auch mal bissl Luft lassen, damit der MT rechnen kann. Coole Sache :D Mit Sleep(10) laufen gerade 10 Threads vollkommen ruckelfrei, man kann während die Threads volle Kanne rechnen den Chart zoomen, verschieben usw... Sauber, DANKE :D.

Code: Alles auswählen

procedure TMyThread.Execute;
var
  i , Cnt: Integer;
begin
  while not terminated do begin
    for i := 0 to 99 do FData[i] := ((7.5 - FNr)/3.5 - 1)/1.2 + Sin(i/5 + Cnt*FNr/50)/5;
    Synchronize(@ShowData);
    Inc(Cnt);
    Sleep(10); // wichtig
  end;
end; 
 
Und mal die Frage an Theo, kriegst man/Du das ohne Threads (evtl. mit mehr Daten) auch so hin?
ChartAndThreadSynchronize_mod.zip
(4.54 KiB) 168-mal heruntergeladen

Antworten