Prioritäten bei Threads

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Hallo,

ich bin immer noch mit meiner "Datenlogger" Software beschäftigt.
Die Software sollte S0 Zählerimpulse aufzeichnen und die aktuelle Leistung berechnen.

Nun zum Problem:
Die S0 Eingänge werden per i2c abgefragt - diese Abfrage sollte im 30ms Zyklus oder schneller geschehen.

Sporadisch (ca. alle 1-2 Stunden) wird ein Impuls nicht erkannt, weil der "Abfragezyklus" länger als 30ms gedauert hat.
Dies geschieht wenn gerade in anderen Threads eine Rechnung gemacht wird.

Hat jemand eine Idee, wie ich den Thread "Einlesen" fix im 30ms Takt hinbekomme.

Hab an multiprocessing gedacht und dann in Linux die Prozesse priorisieren, dann hab ich aber wieder das Problem mit dem
Datenaustausch zwischen "Einlesen" und "Berechnen".

Vielen Dank für eure Tipps.
Daniel
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Antwort: gar nicht.

1. Entscheidet der OS Kernel, wann Python Rechenzeit bekommt und 2. dann kommt noch der (mehr oder minder komplexe) Mechanismus des Thread-Switchings von (C)Python dazu.

Sehr empfehlenswertes Video dazu: https://www.youtube.com/watch?v=Obt-vMVdM8s

Welches Problem hast du denn mit dem Datenaustausch (bzw. wenn es schon Threads hier im Forum gibt gerne auch einen Link dorthin). Es gibt da ja diverse Möglichkeiten.

Gruß, noisefloor
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Hab's noch garnicht versucht.
Hab nur ca. 10 Bool die ich austauschen muss - was ist die simpelste Lösung dafür?
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

musst du Daten von A nach B senden oder teilen sich zwei Programmteile die Daten gemeinsam?

Gruß, noisefloor
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Von A nach B reicht.

Lg
Daniel
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie noisefloor schon richtig sagt - das Python-eigene Thread Management macht Probleme. Wenn du aber multiprocessing verwendest, und in einem Prozess NUR das I2C lesen betreibst, sollte das funktionieren. Denn dann kannst du den I2C-Prozess mit eine Echtzeitprioritaet ausstatten. Wichtig ist aber, das der dann nur das absolut notwendige tut!

Ggf kannst du auch nixh die Kerneloption threaded-irq benutzen. Oder gar einen PREEMPT_RT Kernel nutzen.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Hab alls Test mal ein Script gemacht das nur einliest - bleibt im Bereich 1-3ms, das wäre super.
Hab die Priorität mit "nice" gemacht, funktioniert auch super.

Nun stellt sich noch die Frage, wie die Variablen dem "langsameren" prozess zur verfügung stellen.
Es gibt ja mehrere ansätze, welcher ist zu empfehlen?
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Code: Alles auswählen

import multiprocessing
import time
import os

def slow_proc():
    os.nice(10)
    while True:
        print 'worker1'
        time.sleep(0.1)

def fast_proc():
    os.nice(-10)
    while True:
        print 'worker2'
        time.sleep(0.1)

if __name__ == '__main__':
    slow = multiprocessing.Process(target=slow_proc)
    slow.start()

    fast = multiprocessing.Process(target=fast_proc)
    fast.start()
So funktioniert das ganz gut mit dem priorisieren
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Nun stellt sich noch die Frage, wie die Variablen dem "langsameren" prozess zur verfügung stellen.
Um Daten von A nach B zu transportieren bietet sich eine Queue an. Python hat dafür ein passendes Modul Namens `queue` an Bord.

Wenn die Daten viel schneller kommen als diese von einem Prozess verarbeitet werden können, können aus einer Queue auch mehrere Prozess lesen.

Gruß, noisefloor
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Hallo,

ich habe nun einiges versucht - trozdem ist dei "zykluszeit" manchmal über 30ms.

Kann ich diesen Code "verschnellern", dies ist die Abfrage im schnellen Teil der Software?

lg
Daniel
#i2cdata
channel1_input = not (bool(i2cdata & (2 ** 0)))
channel2_input = not (bool(i2cdata & (2 ** 1)))
channel3_input = not (bool(i2cdata & (2 ** 2)))
channel4_input = not (bool(i2cdata & (2 ** 3)))
channel5_input = not (bool(i2cdata & (2 ** 4)))
channel6_input = not (bool(i2cdata & (2 ** 5)))

#channel1
channel1_ppulse = channel1_input and not channel1_ppulse_hm
channel1_ppulse_hm = channel1_input
if channel1_ppulse is True:
if channel1_active is True:
channel1.impuls()

#channel2
channel2_ppulse = channel2_input and not channel2_ppulse_hm
channel2_ppulse_hm = channel2_input
if channel2_ppulse is True:
if channel2_active is True:
channel2.impuls()

#channel3
channel3_ppulse = channel3_input and not channel3_ppulse_hm
channel3_ppulse_hm = channel3_input
if channel3_ppulse is True:
if channel3_active is True:
channel3.impuls()

#channel4
channel4_ppulse = channel4_input and not channel4_ppulse_hm
channel4_ppulse_hm = channel4_input
if channel4_ppulse is True:
if channel4_active is True:
channel4.impuls()

#channel5
channel5_ppulse = channel5_input and not channel5_ppulse_hm
channel5_ppulse_hm = channel5_input
if channel5_ppulse is True:
if channel5_active is True:
channel5.impuls()

#channel6
channel6_ppulse = channel6_input and not channel6_ppulse_hm
channel6_ppulse_hm = channel6_input
if channel6_ppulse is True:
if channel6_active is True:
channel6.impuls()
time.sleep(0.0005)
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Das ist sequentiell ablaufender Code. Die Crux wird irgendwo in channel.impuls() liegen un die Funktion kennen wir nicht.

Du benutzt hier 6x fast identischen Code. Das schreit regelrecht danach, dass das in Schleifen gelöst und die Daten in Listen gespeichert werden. Als Faustregel gilt: Wenn man anfängt Variablen zu nummerieren, muss man sich Gedanken machen, das zu bündeln.
Dann ist der ganze Abschnitt so um die 10 Zeilen Code, wenn überhaupt. Und du würdest die Fehlerpotentiale erheblich reduzieren.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Im Impuls wird nicht all zu viel gemacht:
def impuls():
# print 'channel1 - PULSE'
last_pulstime.value = act_pulstime.value
act_pulstime.value = time.time()
channel_logger_count.info(str(setpoint))
Ändert dies etwas an der Geschwindigkeit wenn es in einer Schleife gemacht wird?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Muntliger: Jetzt verschiebt sich das zu was auch immer in ``channel_logger_count.info(str(setpoint))`` gemacht wird. Logger können ja beliebig komplizierte Dinge tun.

Ja, an der Geschwindigkeit ändert sich etwas, das ist aber nicht der Punkt. Eine Schleife ist kürzer, leichter lesbar, besser wartbar und damit weniger fehleranfällig. Wenn Du manuelles „loop unrolling“ machen willst, dann ist Python an der Stelle die falsche Programmiersprache.

Was Du auch sein lassen solltest ist das Vergleichen mit literalen Wahrheitswerten. Wobei ``is`` auch erst in aktuellen Python 3-Versionen überhaupt erst erlaubt ist. Und die beiden verschachtelten ``if``-Bedingungen hätte ich auch in *eine* gesteckt.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Faustregel ist: wenn du Echtzeit machen willst, mach keine IO. Du kommst um deine I2C-Calls nicht herum, aber alles andere, was nicht noetig ist, solltest du vermeiden.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

was meinst du mit IO?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Input/Output. Dateien lesen, schreiben, Speicher allokieren (ist in Python nicht zu vermeiden), im Grunde jeder Systemaufruf der das Potential hat, lange auf die Bank geschoben zu werden. Wenn du logging betreibst, dann ist das unnoetig. Das kannst du auch aufsammeln und zB genauso wie die Nutzdaten an deinen zweiten Prozess uebergeben. Das ist dann nur noch ein Aufruf, und eine PIPE (welche der Queue unterliegt) sollte kein Problem darstellen.
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

IO = jegliche Art von Eingabe / Ausgabe Operation, IO steht für Input/Output. IO ist halt immer um ein vielfaches langsamer als die CPU. Sprich: viel IO bremst das Programm.

Gruß, noisefloor
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

noisefloor hat geschrieben: Montag 14. Januar 2019, 19:50 IO = jegliche Art von Eingabe / Ausgabe Operation, IO steht für Input/Output. IO ist halt immer um ein vielfaches langsamer als die CPU. Sprich: viel IO bremst das Programm.
Das ist so nicht richtig. IO ist auch das beschreiben oder lesen von einer Pipe, und das ist zB komplett im Speicher und dem Kernel abgehandelt. Es kostet den Uebertritt in den Kernel, und der muss/soll dann natuerlich auch den wartenden Prozess/Thread aufwecken - aber das ist keine grosse, vor allem aber auch nicht unberechenbare Verzoegerung, wenn man das System ausreichend austariert hat.

Sonst koennten Echtzeit-Prozesse/Threads kaum sinnvolle Arbeit leisten - nur, wenn alles, was sie machen, auch in ihnen berechnet wuerde. Aber zB ein Roboter, der Sensordaten lesen und Aktuatoren bewegen soll, muss das in RT machen, bekommt ja aber Daten zugeliefert von langsamen und ggf. latentz-behafteten Systemen wie einer Kartierung oder aehnlichem.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

__blackjack__ hat geschrieben: Montag 14. Januar 2019, 16:06 @Muntliger: Jetzt verschiebt sich das zu was auch immer in ``channel_logger_count.info(str(setpoint))`` gemacht wird. Logger können ja beliebig komplizierte Dinge tun.

Ja, an der Geschwindigkeit ändert sich etwas, das ist aber nicht der Punkt. Eine Schleife ist kürzer, leichter lesbar, besser wartbar und damit weniger fehleranfällig. Wenn Du manuelles „loop unrolling“ machen willst, dann ist Python an der Stelle die falsche Programmiersprache.

Was Du auch sein lassen solltest ist das Vergleichen mit literalen Wahrheitswerten. Wobei ``is`` auch erst in aktuellen Python 3-Versionen überhaupt erst erlaubt ist. Und die beiden verschachtelten ``if``-Bedingungen hätte ich auch in *eine* gesteckt.

Ok, hab das jetzt alls Schleife gemacht - Danke für den Tipp

was hab ich für eine alternative als der Vergleich mit "is True" oder "is False"?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

'is True' oder 'is False' sowieso niemals. Eigentlich brachst du niemals mit True oder False vergleichen, denn da kommt dann ja auch nur wieder True oder False raus. Also statt

Code: Alles auswählen

if condition == True:
kannst du gleich

Code: Alles auswählen

if condition:
schreiben. Fuer

Code: Alles auswählen

if condition == False:
dann

Code: Alles auswählen

if not condition:
Antworten