Thread Variable Verständnisfrage.

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
haderlump
Beiträge: 185
Registriert: Fr 18. Jan 2013, 09:29
OS, Lazarus, FPC: Windows 10, Windows XP, Lazarus 1.6
CPU-Target: Celeron

Thread Variable Verständnisfrage.

Beitrag von haderlump »

Hallo zusammen
Zur Steuerung meiner Modellbahn gibt es eine procedur "Zugfahrstrasse'.
Da gibt es eine Variable die ich Rucksack getauft habe. Das ist ein Record, der verschiedenen informationen aufnimmt bzw. verteilt.
Diese Variable war bisher global, und hat ihren Zweck perfekt erfüllt. Die procedure Zugfahrstrasse ruft nun ihrerseits weiter proceduren auf, z.B Weichen, Gleise, Signale.
Jede dieser Routinen entnimmt dem Rucksack informationen, oder schreibt welche hinein. Sie wirde quasi wie ein Rucksack duch den ganzen Prozess getragen, deshalb auch der Name.

Nun kommt die Schattenbahnhofsteuerung ins Spiel, diese Steuerung ist ein automatisch timergesteuerter Hintergrundprozess.
Diese Prozess ruft von sich aus den bisher manuell gesteuerten Fahrstrasseneinlauf auf.
Deshalb habe ich den Fahrstrasseneinlauf in threads ausgelagert. Da diese nun gleichzeitig ablaufen können ist es natürlich vorbei mit der globalen Rucksackvariablen.

Ich habe nun folgendes gemacht: Der Fahrstrasseneinlauf bekommt eine eigene, lokale Rucksackvariable, die dann per var Parameter an die anderen Proceduren übergeben wird.
Meine Vorstellung:
bei jedem Thread.crate wird die Variable angelegt und initialisiert. Die Variable wird per Zeiger weitergereicht, bleibt also während dem ganzen Vorgang erhalten.
Erst wenn der Thread gelöscht wird, wird auch die Variable gelöscht.
jeder neue Thread legt eine eigene Variable an.

Meine Frage dazu: Ist das so wie ich mir das vorgestellt habe, oder liegt da meinerseits ein Denkfehler vor?
Für euere Beurteilung wäre ich sehr dankbar.

Gruß Fritz.

MmVisual
Beiträge: 1445
Registriert: Fr 10. Okt 2008, 23:54
OS, Lazarus, FPC: Winuxarm (L 3.0 FPC 3.2)
CPU-Target: 32/64Bit

Re: Thread Variable Verständnisfrage.

Beitrag von MmVisual »

Wenn es geht würde ich auf Threads verzichten. Kann man nicht einen Timer machen, der z.B. alle 100ms die einzelnen Aufgaben auf ruft?
EleLa - Elektronik Lagerverwaltung - www.elela.de

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

Re: Thread Variable Verständnisfrage.

Beitrag von Warf »

Erstmal, wenn ich es richtig verstanden habe, sollte das so funktionieren wie du es dir vorstellst. Aber, warum verwendest du dafür keine OOP? Statt deiner Globalen Variable auf die von Prozeduren zugegriffen wird, hast du eine Klasse die diese Variablen enthält, und benutzt dann statt Funktionen die dann den Status(Rucksack) als Parameter bekommt, machst du das einfach als Methode der Klasse. Genau dafür ist OOP gemacht, um eben nicht mit globalen variablen oder var Parametern jonglieren zu müssen. Jeder thread braucht dann eine eigene Klasseninstanz.

PS:
Jede dieser Routinen entnimmt dem Rucksack informationen, oder schreibt welche hinein. Sie wirde quasi wie ein Rucksack duch den ganzen Prozess getragen, deshalb auch der Name.

In Fachsprache nennt man so eine Variable Zustand (engl State) oder Status, wenn du das so nennst weiß jeder Programmierer direkt was du meinst. Als Rucksack bezeichnet man meist eine art Container mit Maximalen Volumen (siehe Rucksackproblem)

haderlump
Beiträge: 185
Registriert: Fr 18. Jan 2013, 09:29
OS, Lazarus, FPC: Windows 10, Windows XP, Lazarus 1.6
CPU-Target: Celeron

Re: Thread Variable Verständnisfrage.

Beitrag von haderlump »

Danke für die Antworten.
MmVisual: Der Threadstart läuft schon über einen Timer. Das Problem ist folgendes:
Der Fahrstraßeneinlauf würde normal ein paar Mykrosekunden dauern. Es gibt aber mitlerweile Anlagenelemente, die brauchen längere Zeit, z.B. eine Drehscheibe, oder, ich habe auch einen Aufzug auf der Anlage, der 30 Sekunden zum fahren braucht. Der Fahrstraßeneinlauf muss aber dringend abwarten, bis alles erledigt ist. Stell dir vor die Lok fährt auf die Drehscheibe, und die ist noch nicht bereit.
Am Anfang hatte ich den Fahrstraßeneinlauf einfach im Hauptthread. Und die Warterei hat dann das komplette Programm blockiert. Es wurden auch keine Rückmeldungen von der Anlage mehr verarbeitet.
Ich habe diesen Fahrstraßeneinlauf einfach in einen eigen Thread ausgelagert. und dann ging alles.
Nun kann die Schattenbahnhofssteuerung aber Anlagenrückmeldungsgesteuert (Wow was für ein Wort) einen eigenene Thread für den Einlauf starten. Und spätestens jetzt ist es mit der globalen Variablen vorbei.

Warf:
Das mit der OOP ist kein schlechter Gedanke. Der Fahrstraßeneinlauf ist aber kein linearer Prozess, sondern setzt sich dynamisch situationsbezogen aus verschiedenen Proceduren zusammen. Ich hab das mit Klassen versucht, aber das hat sich dann bis zur Unverständlichkeit aufgeblasen.
Die Lösung die ich gefunden habe passt jetzt schon recht gut.
Zum Rucksack:
Die Anfänge des Programms reichen bis in die 80er Jahre zurück. Ich begann damals noch mit Assembler auf einem Schneider CPC128 wenn euch das noch was sagt.
Und da hab ich für ein Register den Begriff Rucksack verwendet. Der Begriff hat sich quasi über die Jahre in mein Hirn eingebrannt, und deshalb will ich das auch nicht abändern.

Herzlichen Dank nochmal
Fritz

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

Re: Thread Variable Verständnisfrage.

Beitrag von kupferstecher »

Hallo Haderlump,

mir ist die Funktion der Variable (des Records) "Rucksack" noch nicht ganz klar. Dennoch ein, zwei Gedanken dazu:

Du sagst du benötigt zwei Instanzen, da die Fahrstraßensteuerung gleichzeitig manuell und automatisch ausgelöst werden kann. Diese Gleichzeitigkeit hast du ja aber eigentlich logisch ausgeschlossen, da sich sonst die Abläufe in die Quere kommen können. Existiert diese Gleichzeitigkeit aber nicht, weil die Steuerung genau das verhindern muss, dann sollte wiederum die einfache Instanz, z.B. durch die globale Variable ausreichen.
Kann es sein, dass im Record die Übergabewerte an die Fahrstraßensteuerung und interne Arbeitsvariablen vermischt sind und daher das Problem auftritt?
Wenn das der Fall ist, würde ich das trennen. Vermutlich gibt es nicht viele Werte, die der Prozedur "Zugfahrstrasse" übergeben werden müssen. Diese Werte könnte man in ein Record oder eine Klasse "Stellauftrag" zusammenfassen. Der Prozedur "Zugfahrstraße" würde man diese Variable übergeben, aber nicht sofort mit der Abarbeitung starten, sondern in einen Puffer packen, z.B. einen FIFO, der vom Hintergrundthread ständig abgefragt wird und wenn vorhanden einen Stellauftrag nach dem anderen abarbeitet. Der Stellauftrag kann dann von der manuellen Eingabe, von der Schattenbahnhofsteuerung oder über eine Schnittstelle von weiteren Bediengeräten kommen ohne zu Kollisionen zu führen. Da würde es sich dann vielleicht lohnen die Instanzen der Stellaufträge dynamisch zu erzeugen.

MmVisual
Beiträge: 1445
Registriert: Fr 10. Okt 2008, 23:54
OS, Lazarus, FPC: Winuxarm (L 3.0 FPC 3.2)
CPU-Target: 32/64Bit

Re: Thread Variable Verständnisfrage.

Beitrag von MmVisual »

>> Und die Warterei hat dann das komplette Programm blockiert.

Das ist der falsche Ansatz. Man wartet niemals im Code. Das ist eine Grundregel. Wenn man diese befolgt, dann braucht es auch keinen zweiten Thread.

Steuerungen programmiert man in der Regel mit Abläufen. Beispiel:

Case Schrittvariable

0: // Grundstellung
Schalte das Ein = FALSE;
If Starte Then Schrittvariable += 1;

1: If Das And Das And Das Then
Schalte das Ein = TRUE;
Schrittvariable += 1;
end if

2: // mache den nächsten Schritt

End;

Diese Case Kette wird immer verlassen und z.B. alle 100ms aufgerufen.
Solche Case Ketten sind sehr schnell in der Bearbeitung und blockieren nicht das System. Außerdem weiß man zu jeder Zeit bei welchem Schritt welcher Prozess sich befindet.
Mehrere Case Ketten sind je Prozess möglich. Das könnte man auch sehr leicht in Klassen auslagern, um einfache Instanzen bilden zu können.
EleLa - Elektronik Lagerverwaltung - www.elela.de

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

Re: Thread Variable Verständnisfrage.

Beitrag von Warf »

So wie ich es verstanden habe schließe ich mich WmVisual an, Threads sind für solche Steuerungen nicht die beste wahl (bzw nicht direkt).

Da hast du zwei Möglichkeiten, entweder wie WmVisual meint das ganze als "State Machine" bzw. Automaten zu implementieren: man speichert den Status und hat dann solchen code:

Code: Alles auswählen

case currentState of
0: Beginne Zyklus;
1: Schritt 2
2: Schritt 3
...
end;
if IrgendwasHatSichGeändert then currentState += 1;


Die andere Möglichkeit ist den Zyklus verteilt zu bauen mittels OOP. Somit hat jedes Objekt seine eigene klasse und kann z.B. über events koordiniert sein. Bei deinem Beispiel mit der Drehscheibe würde dein Zugobjekt also wenn es vor die Drehscheibe gefahren ist die Funktion DrehZu(Winkel, Event) aufrufen, wobei event ein Methodenzeiger ist einer Funktion die aufgerufen werden soll wenn die Aktion erfüllt wurde. Die Drehscheibe kann dann intern eine Warteliste führen mit allen anfragen und somit garantieren das alle anfragen in der richtigen Reihenfolge ausgeführt werden:

Code: Alles auswählen

// Zug versucht Drehscheibe zu nehmen
  DrehScheibe.DreheZu(MeinGleis, KannAuffahren)
 
procedure TZug.KannAuffahren(Sender: TDrehscheibe);
begin
  Self.FahreAuf(Sender);
  Sender.DreheZu(ZielGleis, KannAbfahren);
end;
 
procedure TZug.KannAbfahren(Sender: TDrehScheibe);
begin
  Self.FahreAb(sender);
  ... // was auch immer danach kommen mag
end;
 
procedure TDrehScheibe.Update();
begin
  If AktuelleAktion.Beendet then
  begin
    AktuelleAktion.Event(Self); // feuere übergebenes event
    AktuelleAktion = WarteSchlange[0];
    WarteSchlange.Delete(0);
  end;
  AktuelleAktion.MachIrgendwas
end;
 
procedure TDrehScheibe.DreheZu(Ziel, Event);
var NeueAktion: TDrehscheibenAktion; // z.b. ein record
begin
  neueAktion.Beendet = false;
  neueAktion.Ziel = Ziel;
  NeueAktion.Event = Event;
  Warteschlange.Add(NeueAktion); // Z.B. TList
end;


Jedes Objekt hat dann eine update Funktion mit der die nächsten schritte ermittelt werden (ähnlich wie bei der State Machine wie sie WmVisual vorgeschlagen hat) und die Events ändern dann den State.

So werden z.B. moderne Videospiele geschrieben, es ist praktisch die OOP Variante der State machine

haderlump
Beiträge: 185
Registriert: Fr 18. Jan 2013, 09:29
OS, Lazarus, FPC: Windows 10, Windows XP, Lazarus 1.6
CPU-Target: Celeron

Re: Thread Variable Verständnisfrage.

Beitrag von haderlump »

Danke für die Antworten
Kupfrestecher:
Der Rucksack-record sieht folgendermaßen aus:

Code: Alles auswählen

Trucks = Record
 
    Fahrtrichtung,      //True = Die Fahrt geht von rechts nach links
    ziel_gefunden,      //Es wurde ein Element gefunden bei dem die Ziel-Eigenschaft gesetzt wurde.
    hp2,                //Die Fahrt führt über die Ablenkung einer Weiche
    vr0,vr1,vr2,        //Vorsignalstellungen
    hilfsaufloesung,    //Markierung für die Verwendung des Threads
    rangierstrasse,     //   w.V.
    sperrfahrt,         //   w.V.
    flankenschutz,      //   w.V.
    verlaengerung,
    suchrichtung_l,
    rot1, rot2,ge,gn,   //Farben der Signallampen True = leuchtet
 
    signalsuche   : boolean;  //Wird gesetzt, wenn eine fahrstrasse verlängert wird, also der startabschnitt bereits festgelegt ist
    Fahrregler,
    letztes_hauptsignal_stellung,
    hp2_anforderungen,
    hp2_Markierungen:integer;
    erstes_signal,
    letztes_signal : Pdaten_element;
    haltfall : PKonto;
    Loknummer1:string[20];
end ;                           

So nun mal derkomplette Ablauf.
Manuell:
Per Menü wähle ich 'Zugfahrstraße aus. Diese Aktion bestimmt nun was der Mausklick ins Gleisbild bewirkt.
1. Klick Das Startelement (muss ein Signal sein, alles andere wird ignoriert) wir ausgewählt und ein Zeiger darauf zwischengespeichert.
2. Klick Das Zielelement wird ausgewählt, und darin die Eigenschaft 'Ziel_gesetzt' gesetzt.
Jetzt wird der Thread Zugfahrstraße gestartet.
Dieser holt sich nun den Zeiger auf das Startelement.
Dann wird der Rucksack und andere Dinge entsprechend initialisiert, ein freier Fahrtregler angefordert, und auch in den Rucksack kopiert.
Dann kommt die Suche nach dem Ziel.
Für jedes Element gibt es eine oder mehrere Proceduren die sich darum kümmern was das Element kann.
Beispiel: ein einfacher Bahnhof
/---------------Gl1-------- SigN1----------\
-----SigA---W1-------------Gl2---------SigN2----------W2----SigF---
Zeiger auf SignalA wird gespeichert
bei Signal N2 wird das Ziel gesetzt.
Routing: Beim Signal A gibt es einen Pointer auf W1
Jetzt gibt es einen Arbeitsverteiler. Der bestimmt wie es weiter geht.
In unserem Fall mit der Weiche.
Dort findet sich u.a ein Pointer auf Gleis2
und der Arbeitsverteiler routet dort hin weiter.
weiter geht es zum Signal N2. Dort ist die Eigenschaft 'Ziel_gesetzt'. Dieses Ziel_gesetzt wird in den Rucksack übertragen.
Nun liegt der Fahrweg fest.
jetzt geht es zurück.
ins Gleis2 wird der Fahrregler eingetragen und das Gleis festgelegt(steht dann für andere Fahrstraßen nicht mehr zur Verfügung)
Zurück zur Weiche. auch hier Fahrregler eintragen und festleget. Zurück zum Signal
Das kann jetzt auf grün gestellt werden.
Nun wird noch die Lok in den Regler kopiert und der Fahrbefehl gegeben.
Das Prozedere mag kompliziert erscheinen, man könnte ja auch vorab festlegen wie die Weichen zu stehen haben.
Bei kleinen Anlagen mag das auch richtig sein, doch mit jeder Weiche vervielfachen sich die Fahrmöglichkeiten und die Liste mit den Fahrmöglichkeiten bläst sich auf.
Wie beschrieben gibt es ja verzögernde Elemente.
Der ganze Prozess kann nun aber auch automatisch durch die Schattenbahnhofssteuerung ausgelöst werden.
Mit einem neuen thread läuft das jetzt wunderbar. Durch ein Flag in jedem geroutetem Element wird verhindert, dass ein Thread die Daten des anderen verändert.
Wie bei jedem Modellbahnprogramm muss man der Software ja mitteilen, wie die Anlage denn aussieht.
Das Läuft über mehrfach verkettete Listen.
Wenn man an der Anlage etwas ändert muss man nur vergleichsweise geringe Korrekturen in der Liste vornehmen und schon geht wieder alles.
Bei Verfahren mit den vorgegebenen Wegen werden bei größeren Anlagen umfangreiche Änderungen fällig.
Mein Gott, jetzt ist das wieder ein Roman geworden.
Wer an diesem hochinteressantem Projekt interessiert ist, kann sich ja per PM bei mir melden. Ich muß das nicht mit ins Grab nehmen.
Es gäbe da auch noch viel zu tun. z.B. Ein Gleisbildeditor. Schnittstellen zu Handyapps.....
Gruß Fritz

Antworten