Problem mit threads und globalen variablen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
wese85
User
Beiträge: 8
Registriert: Dienstag 28. August 2018, 11:51

Mittwoch 7. November 2018, 13:11

Hallo,

Ich habe ein Problem beim ausführen mehrer Threads welche auf die selben
Globalen Variablen zugreifen müssen.

Das Problem taucht nicht gleich auf erst nach ein paar durchläufen verhält sich
ein Ausgang des PiFace nicht mehr wie erwartet (blinkt mit sehr hoher Frequenz oder dauerhaft).

Nun ist die Frage ob das daher kommt das es irgendwann zu Überschneidungen zwischen
Schreiben der Variable in der Hauptschleife und lesen in einer der Threads.

für etwas Hilfe wäre ich sehr dankbar!

Code: Alles auswählen

import pifacedigitalio as pfio
from time import sleep
from concurrent import futures

wertA = [0.800,0.800,0.800]
wertB = [0.200,0.200,0.200]

pfio.init()
Thread = futures.ThreadPoolExecutor(max_workers=3)

def test1():
    while(1):
        pfio.digital_write(0,1)
        sleep(wertA[0])
        pfio.digital_write(0,0)
        sleep(wertB[0])

def test2():
    while(1):
        pfio.digital_write(1,1)
        sleep(wertA[1])
        pfio.digital_write(1,0)
        sleep(wertB[1])
        
def test3():
    while(1):
        pfio.digital_write(2,1)
        sleep(wertA[2])
        pfio.digital_write(2,0)
        sleep(wertB[2])
        
Thread.submit(test1)
Thread.submit(test2)
Thread.submit(test3)

while(1):
    print('A')
    wertA[0] = 0.800
    wertA[1] = 0.800
    wertA[2] = 0.800
    wertB[0] = 0.200
    wertB[1] = 0.200
    wertB[2] = 0.200
    sleep(1)
    print('B')
    wertA[0] = 0.300
    wertA[1] = 0.300
    wertA[2] = 0.300
    wertB[0] = 0.700
    wertB[1] = 0.700
    wertB[2] = 0.700
    sleep(1)
mfg wese
Sirius3
User
Beiträge: 8625
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 7. November 2018, 13:49

Niemals globale Variablen benutzen und erst recht nicht im Zusammenhang mit Threads. Hier wird es aber wahrscheinlich die Bibliothek zur Hardwareansteuerung sein, die nicht mit Threads umgehen kann.
Für Deine Anwendung brauchst Du gar keine Threads. Mache eine Liste mit [(Zeitpunkt, Port, Wert), ...] die Du in einer Schleife abarbeitest. Bei sich wiederholenden Vorgängen sortierst Du einfach die Wiederholung richtig in die Liste ein.

Zum Code: drei Funktionen, die quasi identisch sind faßt man zu einer mit Parametern zusammen. while ist keine Funktion, daher keine Klammern um die Bedingung. Statt 1 nimmt man True.
Benutzeravatar
__blackjack__
User
Beiträge: 1450
Registriert: Samstag 2. Juni 2018, 10:21

Mittwoch 7. November 2018, 14:45

Was hier IMHO auch sehr unschön ist, sind die zusammengehörigen Werte in zwei ”parallelen” Listen.

Code: Alles auswählen

    **** COMMODORE 64 BASIC V2 ****
 64K RAM SYSTEM  38911 BASIC BYTES FREE
   CYBERPUNX RETRO REPLAY 64KB - 3.8P
READY.
█
wese85
User
Beiträge: 8
Registriert: Dienstag 28. August 2018, 11:51

Mittwoch 7. November 2018, 15:34

Hallo,

Ich habe vielleicht ein bisschen zu wenig erklärt warum es geht
bei dem Codebeispiel handelt es sich um eine stark vereinfachte
Version des Originals, das eigentlich Programm ist eine PID Regelung
die in der Hauptschleife Sollwerte, Istwerte und eben Stellwerte berechnet.
Bei den Stellwerten handelt es sich um genau diese Globalen Variablen
welche deshalb leider nicht alle mit einer Schleife abgearbeitet werden können, da
die angesteuerten Heizbereiche eben unterschiedliche Stellwerte benötigen welche
sich jede Sekunde ändern. Die einzelnen Threads für die Heizungsansteuerung können
deshalb dazwischen keine Pausen machen.
Zu dem das die Bibliothek der Hardware ein Problem hat, muss man sagen das ohne
Veränderung der Werte in der Hauptschleife, die PWM von der Hardware erzeugt wird.

Noch zu sagen bei dem WertA handelt es sich um die OnZeit und WertB die Offzeit der Heizung!


Gruß wese
Benutzeravatar
__blackjack__
User
Beiträge: 1450
Registriert: Samstag 2. Juni 2018, 10:21

Mittwoch 7. November 2018, 16:07

Die Erklärung ändert nichts daran das man globale Variablen an sich schon nicht machen sollte und erst recht nicht mit Threads. Auch das die beiden zusammengehörigen Werte in parallelen Listen ist auch dann noch unschön. Genau wie die Namen dieser beiden Listen. Wenn die `on_times` und `off_times` statt `wertA` und `wertB` heissen würden, bräuchte man das nicht extra erklären. ;-)

Wenn die Bibliothek zum ansteuern der Hardware nicht thread-sicher ist, könnte man das mit einem `threading.Lock` absichern, oder einen extra Thread für die Ansteuerung erstellen und dem die Aufträge über eine `queue.Queue` übermitteln.

Code: Alles auswählen

    **** COMMODORE 64 BASIC V2 ****
 64K RAM SYSTEM  38911 BASIC BYTES FREE
   CYBERPUNX RETRO REPLAY 64KB - 3.8P
READY.
█
Sirius3
User
Beiträge: 8625
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 7. November 2018, 16:12

@wese85: das vereinfacht doch das Problem. Du hast eine Liste mit [(Regler, Werte), ...], die Du nach Wert sortierst. Jede volle Sekunde werden alle Regler auf 1 gestellt und die Liste nach und nach abgearbeitet um den Regler wieder auf 0 zu stellen.
Wobei das mit dem On-Off irgendwie überflüssig ist, wenn Du doch PWMs hast.
wese85
User
Beiträge: 8
Registriert: Dienstag 28. August 2018, 11:51

Mittwoch 7. November 2018, 16:34

Sirius3 hat geschrieben:
Mittwoch 7. November 2018, 16:12
@wese85: das vereinfacht doch das Problem. Du hast eine Liste mit [(Regler, Werte), ...], die Du nach Wert sortierst. Jede volle Sekunde werden alle Regler auf 1 gestellt und die Liste nach und nach abgearbeitet um den Regler wieder auf 0 zu stellen.
Wobei das mit dem On-Off irgendwie überflüssig ist, wenn Du doch PWMs hast.
Das Problem ist das man halt leider nicht alle immer zu gleichen zeit auf 1 stellen kann da die Temperatur an der einen Heizung
schon etwas weiter ist als an den anderen zwei z.B
bei der on-offzeit ist es so das sie dadurch das pwm-signal erzeugt [hardwareouput 1 sleep(0.800) hardwareoutput 0 sleep(0.200)=pwm]

ich glaub ich versteh es falsch was du meinst :roll:
wese85
User
Beiträge: 8
Registriert: Dienstag 28. August 2018, 11:51

Mittwoch 7. November 2018, 16:47

und zum Thema Globale Variablen mit Threads böse habe ich verstanden, die unsaubere Schreibweise sehe ich auch ein
und entschuldige mich dafür aber wie bekomme ich werte von außerhalb welche erst nach dem starten des threads berechnet
werden da rein.
Benutzeravatar
__blackjack__
User
Beiträge: 1450
Registriert: Samstag 2. Juni 2018, 10:21

Mittwoch 7. November 2018, 17:06

@wese85: Zum Beispiel über Queues oder Objekte mit einer `set_*()`-Methode und bei Threads dann eventuell das ganze noch mit einem `threading.Lock` gegen gleichzeitigen Zugriff abgesichert.

Code: Alles auswählen

    **** COMMODORE 64 BASIC V2 ****
 64K RAM SYSTEM  38911 BASIC BYTES FREE
   CYBERPUNX RETRO REPLAY 64KB - 3.8P
READY.
█
__deets__
User
Beiträge: 3750
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mittwoch 7. November 2018, 17:17

wese85 hat geschrieben:
Mittwoch 7. November 2018, 16:47
und zum Thema Globale Variablen mit Threads böse habe ich verstanden, die unsaubere Schreibweise sehe ich auch ein
und entschuldige mich dafür aber wie bekomme ich werte von außerhalb welche erst nach dem starten des threads berechnet
werden da rein.
Du brauchst einfach keine Threads. Es gibt keinen Grund, warum nicht *eine* Schleife reicht. In dieser einen Schleife musst du dann eben drei PID-Regeler durchrechnen, und die resultierende Stellwerte setzen. Aber das bringt keine Thread-Notwendigkeit mit sich, sondern nur bessere Code-Organisation.

Code: Alles auswählen

class PID:

     def __init__(self, p, i, d):
         self._p, self._i, self._d =p, i, d
         self._error = 0
         self._target = 0
         self._t_minus_one = None

    def compute(self, elapsed_time, current_value):
           if self._t_minus_one is not None:
                    pf = (current_value - self._target) * elapsed_time
                    df = (current_value - self._t_minus_one) * elapsed_time
                    self._error += df
                    return self._p * pf + self._d * df + self._i * self._error
           self._t_minus_one =  current_value


wohnzimmer, schlafzimmer, hundehuette = PID(...), PID(...), PID(...)

then = time.time()
while True:
           time.sleep(1)
           now = time.time() 
           elapsed = now - then
           then = now
           set_wohnzimmer(wohnzimmer.compute(elapsed_time, messe_wohnzimmer_temperatur())
           ...
Der PID ist jetzt so natuerlich aus dem Aermel geschuettelt und nicht korrket, aber die Idee sollte klarwerden.
Sirius3
User
Beiträge: 8625
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 7. November 2018, 17:30

Du hast also kein Hardware-PWM sondern willst es per Software simulieren?

Beispiel:
R1 an
R2 an
R3 an
warte 0.3
R3 aus
warte 0.2
R2 aus
warte 0.1
R1 aus
warte 0.4
springe an Anfang

Dann war R1 0.6, R2 0.5 und R3 0.3 Sekunden an. Alles in einer Schleife.
wese85
User
Beiträge: 8
Registriert: Dienstag 28. August 2018, 11:51

Donnerstag 8. November 2018, 11:36

Danke das stimmt das würde auch funktionieren (habs getestet), ich hab mich zu sehr auf die
Methode mit den Threads festgelegt. Hatte aber auch ein Grund denn ich hab noch 3 LEDs die
auch über ein Thread in verschiedenen Frequenzen blinken lasse, je nach dem welche Zustand
die Heizungen aufweisen.

Da wäre ja dann das Problem mit dem Zeitverzug von 1sekunde!
__deets__
User
Beiträge: 3750
Registriert: Mittwoch 14. Oktober 2015, 14:29

Donnerstag 8. November 2018, 11:49

Das Grundprinzip ist doch ueberall das gleiche. Du kannst auch 1000 Sachen machen. Du musst nur deine Wartezeiten auf die Reihe bekommen. Du kannst entweder zB mit einem festen Takt von 0.1ms warten, und dann gucken, ob irgendwas an oder aus zu schalten ist. Oder du bestimmst immer den genauen Schritt bis zum naechhsten Ereignis, und wartest dynamisch lange.
Benutzeravatar
__blackjack__
User
Beiträge: 1450
Registriert: Samstag 2. Juni 2018, 10:21

Donnerstag 8. November 2018, 15:16

Beim dynamisch lange warten könnte das `sched`-Modul aus der Standardbibliothek eventuell hilfreich sein.

Code: Alles auswählen

    **** COMMODORE 64 BASIC V2 ****
 64K RAM SYSTEM  38911 BASIC BYTES FREE
   CYBERPUNX RETRO REPLAY 64KB - 3.8P
READY.
█
wese85
User
Beiträge: 8
Registriert: Dienstag 28. August 2018, 11:51

Freitag 9. November 2018, 14:43

Danke! Alles tut, immer sehr hilfreich hier wenn man mal vesteckt. :mrgreen:
Antworten