kontrolliertes Beenden eines im Hintergrund laufenden Python-Programms

L I V E Stammtisch ab 20:30 Uhr im Chat
  • Hallo,

    ich habe mir ein Python-Programm geschrieben, mit dem ich auf einem Raspberry Pi Messdaten erfasse. Daher läuft es dauernd im Hintergrund. Nun möchte ich dieses Programm zu einem späteren Zeitpunkt (z. B. vor dem Herunterfahren) ordnungsgemäß beenden. In meinem Fall bedeutet dies, dass es noch bis zu einer bestimmten Zeile weiterlaufen soll und dann beendet wird.

    Ich stelle mir die Umsetzung wie folgt vor:

    Nun müsste man von außen noch irgendwie diese Variable auf True setzen. Mir ist klar, dass das nicht direkt geht.

    Meine Holzhammer-Lösung wäre eine Textdatei, in die neue Soll-Werte für stop geschrieben werden. Das Programm würde dann vor der if-Abfrage stop auf den Wert aus der Textdatei setzen.

    Elegant finde ich diese Lösung aber nicht gerade, da unter anderem unnötige Schreibzyklen anfallen.

    Bemerkung: Ich nehme bewusst in Kauf, dass das Beenden in Abhängigkeit von der konkreten Realisierung meines Programms dann ein Weilchen dauern kann.

    Vielen Dank schonmal.

    rt42

  • kontrolliertes Beenden eines im Hintergrund laufenden Python-Programms? Schau mal ob du hier fündig wirst!

  • Hallo,

    vielen Dank für die schnellen Antworten.

    Die von euch vorgeschlagene Möglichkeit besteht also darin, auf das SIGTERM oder SIGINT zu lauschen. Erscheint mir logisch.

    Allerdings bin ich darüber hinaus auch an allgemeiner Kommunikation mit dem bereits laufenden Python-Programm interessiert. Ich habe mir noch eine weitere Möglichkeit überlegt, bei der ich allerdings Probleme hab. Hier ist der Code:

    Dieses Programm kann ich über die Konsole bequem beenden, indem ich "stopstop" eingebe und Enter drücke. Dass meine Eingabe durch die Ausgabe optisch in Fetzen gerißen wird, scheint keine technischen Probleme zu machen. Soweit funktioniert das also ganz gut und ließe sich auch zur Weiterleigung von anderen Parametern nutzen.

    Das Problem ist Folgendes: Ich starte das Programm mit python programm.py& und beende dann die Konsole. Das Programm läuft dann wie gewünscht weiter. Zu einem späteren Zeitpunkt schaffe ich es allerdings nicht mehr, mit dem Programm zu "kommunizieren". Kann man dieses Problem lösen?

  • Installier dir das Programm screen und lass das Script ohne " & " dann in einem Screen laufen.

    Aber ich glaube nicht das du mit dieser Variante glücklich wirst, oder willst du vor jedem reboot/shutdown erst "stopstop" eintippern? :fies:

  • Danke, werd ich mir bald mal anschauen.


    Bin gerad noch etwas durch Netz gesurft und hab was gefunden, was mir vielleicht weiterhilft: named pipes

    Der Wikipedia-Artikel dazu ist eigentlich ganz aufschlussreich.

    mit

    Code
    mkfifo QuatschKanal


    erstellt man eine neue named pipe namens QuatschKanal.

    Mit

    Code
    cat QuatschKanal

    kann man dann auslesen und mit

    Code
    echo "text" > QuatschKanal

    kann man was reinschreiben.

    Das könnte ich zur Kommunikation mit meinem Skript zur Messdatenerfassung nutzen. In diesem könnte ich dazu os.system() und os.popen() verwenden. Vielleicht kann ich auch direkt von Python aus drauf zugreifen, wenn es sich wie eine Datei verhält.

    Dass ich dann einen Befehl zum Beenden eingeben muss, stört mich nicht, da ich zum Herunterfahren ein extra Python-Skript nutzen werde.:)

    Diese Lösung hat einen angenehmen Nebeneffekt. Kurz vorm tatsächlichen Beenden kann das Messdatenerfassungsprogramm über die named pipe sein Beenden ankündigen, sodass das steuernde Skript kurz darauf das Herunterfahren einleiten kann.

    Sind euch wichtige Nachteile von named pipes bekannt?

  • iehrks pfui bäh!
    Bitte nehme Abstand davon externe Programme wie cat mithilfe subprocess (besser als os.) aus Python heraus auszuführen. Mach das lieber nativ indem du z.B. einfach nur die Datei mit Python öffnest und ausließt.


    Es gibt wie immer mehrere Wege. Da wir aber nicht das Große und Ganze kennen, können wir nur schwer abschätzen was Du letztlich überhaupt machen willst.

    Eine weitere Möglichkeit wäre zB auch auf Basis eines Client/Server Sockets.......

    Wobei ich eigentlich das mit signal am elegantesten finde, da das Script dann auch beim Reboot sauber beendet wird, und nicht † getötet.

  • Hallo,

    das `os`-Modul unterstützt direkt `mkfifo`, siehe Doku. Die Pipe verhält sich wie eine Datei. Damit solltest du dann alles haben, was du willst.

    Wenn es essentiell wichtig ist, dass bestimmte Schritte vor dem Beenden des Python-Skripts wichtig sind, dann würde ich trotzdem noch Signal-Handling einbauen. Nur für den Fall, dass der Pi mal runtergefahren wird, ohne dass du das Skript beendest.

    Gruß, noisefloor

    Hallo,

    apropos: was ist eigentlich diese "dies und jenes", was zum Ende hin noch unbedingt ausgeführt werden muss?

    Gruß, noisefloor

    Einmal editiert, zuletzt von noisefloor (13. März 2015 um 21:49)

  • Hi all,


    Forumsuche nach: signal SIGINT
    ...


    Fast ;) ... SIGTERM wäre wohl sinnvoller. Bekommt jeder Prozess beim shutdown gesandt.
    SIGINT ist "normalerweise" CTRL+C und setzt eine Terminalsession voraus ...


    ...
    ... darin, auf das SIGTERM oder SIGINT zu lauschen. Erscheint mir logisch.
    ...


    wie gesagt, SIGTERM ... SIGINT wirst Du nicht erhalten.


    ... Bitte nehme Abstand davon externe Programme wie cat mithilfe subprocess (besser als os.) aus Python heraus auszuführen. Mach das lieber nativ indem z.B. du einfach nur die Datei mit Python öffnest und ausließt.
    ...


    Das ist vermutlich in Python nicht anders als in C ... entweder oder ...
    Statt eines Programms in Python/C kannst Du sonst gleich einen bash-script schreiben ;)


    ...
    ... sodass das steuernde Skript kurz darauf das Herunterfahren einleiten kann.
    Sind euch wichtige Nachteile von named pipes bekannt?
    ...


    Nö ... verwende ich im Falle eines Falles auch.
    Das scheint mir in diesem Fall aber unnötig.
    Das mit dem TERM-Signal funktioniert genau so sauber.

    cheers,
    -ds-


  • Fast ;) ... SIGTERM wäre wohl sinnvoller. Bekommt jeder Prozess beim shutdown gesandt.
    SIGINT ist "normalerweise" CTRL+C und setzt eine Terminalsession voraus ...

    Die Suchbegriffe führten zum passenden Ergebnis, wo auch SIGTERM erwähnt wird :fies: Suche nach 'signal SIGTERM' wäre dann gar der erste treffer... Hatte das hier irgendwo auch schon mal genauer erklärt, hab aber nicht mehr die Muse für Andere die Forumsuche zu bemühen ..

  • Hab euren Vorschlag bezüglich SIGTERM mal in einem Beispiel-Programm umgesetzt. Es funktioniert wie gewünscht.:) Vielen Dank!:danke_ATDE:

    Hier ist es:

    Das time.sleep(10) in der if-Anweisung ist natürlich sinnlos. Ich hab das nur eingefügt, um zu erkennen, ob das Programm nicht doch noch radikal abgewürkt wird. Aber das ist nicht der Fall.

    Also das kann ich dann so in meinem Programm umsetzen.

    Die named pipes merk ich mir mal für später, falls ich weitergehende Kommunikationsmöglichkeiten brauche. Danke übrigens für den Hinweis zum direkten Erstellen von named pipes aus Python heraus mittels os.mkfifo().

    Dass ich für ordnungsgemäßes Beenden das SIGTERM-Signal dann aber weiterhin abfangen sollte, leuchtet mir ein.

    Bezüglich der Frage nach dem dies und jenes, was vor dem Beenden ungestört ablaufen soll:
    Das Programm erfasst Messdaten und schreibt diese in Dateien. In größeren Intervallen werden mit matplotlib (ich find die echt toll) daraus Graphen gezeichnet, die in eine statische html-Seite eingebunden werden. Diese wird von dem Raspberry Pi mit nginx gehostet. Ich möchte nicht, dass das Programm mitten beim Zeichnen oder beim Schreiben einer Datei beendet wird. Ein Durchlauf der Schleife soll vorher fertig sein.

    Wenn ihr keine Verbesserungsvorschläge zu dem Beispiel habt, würde ich diesen Thread dann als erledigt markieren.

  • Bitte gewöhn dir es wieder ab 'global' zu verwenden, das ist ziemlich dirty und sollte immer vermieden werden.

    Dein Code sollte wenn dann eher so aussehen:

  • Dein Verbesserungsvorschlag entspricht leider nicht meinen Wünschen. Bei deinem Vorschlag können nach Erreichen von SIGTERM noch fest definierte Befehle abgearbeitet werden und dann wird das Programm direkt aus der signal_handler-Methode heraus beendet.

    Ich möchte allerdings, dass die Anweisungen im Schleifen-Block ungestört abgearbeitet werden und dass das Programm am Ende des Blocks beendet wird, so vorher ein SIGTERM eingetroffen ist. Das geschieht doch hoffentlich so in meinem Beispiel-Programm, oder irre ich mich hierin?

    Was könnte in diesem konkreten Programm bezüglich der globalen Variable schief gehen?

  • Du kannst eine Variable, nennen wir sie mal THREAD_RUN, erstellen und auf True initialisieren. Wenn der Handler dann das SIGTERM empfängt, setzt du THREAD_RUN auf False und machst nicht mehr.
    Das bewirkt, dass deine Schleife bis zum Ende durchläuft. Danach wird das Programm normal weiter abgearbeitet. Man kommt aber bei meiner Version nicht um das "global" nicht rum.

    Code
    while THREAD_RUN:
    ...
    Code
    def signal_handler():
         global THREAD_RUN
         THREAD_RUN = False


    meigrafd: Hab letztens ein ähnliches Problem gehabt, allerdings lief die Funktion bei mir noch in einem eigenem Thread, sodass ich mich für "global" Version entschieden hatte um schnell Variablen zwischen mehreren Threads schreiben zu können. Bin in Python auch noch nicht so begabt, allerdings hatte diese Lösung die geringste Arbeit bereitet. Wenn du da eine elegantere, aber genauso einfache Lösung hättest, würde ich mich freuen ;)

    Einmal editiert, zuletzt von driftmonster (15. März 2015 um 14:11)

  • Hi,
    globale Variablen lassen sich manchmal halt nicht vermeiden. Das ist in C nicht anders. Allerdings gibt es oft Callback-/Event-Handler, denen man beim Initialisieren einen benutzerdefinierten Zeiger mitgeben kann.
    Ich hab' aber jetzt keine Ahnung, ob so was auch in Python möglich ist ...

    cu,
    -ds-

  • Ja klar, es lässt sich nicht immer vermeiden. Man sollte halt so gut es geht global Setzungen vermeiden. Das wollte ich damit eigentlich aussagen...

    Dennoch finde ich die Lösung des TE's etwas zu umständlich, ein paar Zeilen zu viel Code :fies:
    Geht den Ablauf mal gedanklich durch. Am Anfang der while wird geprüft "trifft meine Bedingung zu? Ja? Oke dann starte ich die Schleife". Dann wird der Code innerhalb der while von oben nach unten abgerattert. Anschließend beginnt das ganze wieder von Vorne. Trifft die Bedingung nicht mehr zu wird nichts ausgeführt und die anderen Programmzeilen werden abgearbeitet (nach dem while Block)

    Ich weiß leider immer noch nicht genau was der TE letztlich überhaupt vor hat - leider beschreibt er das nicht und zeigt nur irgendwelche Anschauungsbeispiele die aber laut eigener Aussage nicht wirklich dem entsprechen was er letztlich machen möchte....... Also sorry aber meine Glaskugel ist in Reparatur. Keine Ahnung was genau Deinem Wunsch entspricht.
    Es gibt Immer unzählige Möglichkeiten, nicht nur eine. Das Ergebnis is das selbe, der Weg zum Ziel aber verschieden. Nur kennen Wir nicht wirklich das tatsächliche Ziel.

    Man kann genauso gut auch das machen was ich im 2.Absatz hier beschrieben habe:

    [code=php]
    import signal
    import sys
    import time

    stop = False

    def signal_handler(signal, frame):
    global stop
    print "Beenden wird in die Wege geleitet."
    stop = True

    signal.signal(signal.SIGTERM, signal_handler)

    while not stop:
    time.sleep(1)
    print "ein Durchlauf"

    print "Ende"
    [/php]

  • Ok, mein Code war etwas umständlich. Die zuletzt genannte Version von meigrafd entspricht meinen Anforderungen.

    Vielen Dank für eure Hilfe.:danke_ATDE:

    zum Verwendungszweck:

    Zitat


    Das Programm erfasst Messdaten und schreibt diese in Dateien. In größeren Intervallen werden mit matplotlib (ich find die echt toll) daraus Graphen gezeichnet, die in eine statische html-Seite eingebunden werden. Diese wird von dem Raspberry Pi mit nginx gehostet. Ich möchte nicht, dass das Programm mitten beim Zeichnen oder beim Schreiben einer Datei beendet wird. Ein Durchlauf der Schleife soll vorher fertig sein.

    Damit auch andere Leser was davon haben:


    Zusammenfassung der Ergebnisse
    Das kontrollierte Beenden des Programms soll gewährleistet werden. Dazu wird von diesem das SIGTERM abgefangen, um dann die benötigten Aktionen einzuleiten. Das SIGTERM wird bespielsweise dann gesendet wenn man den Prozess mit

    Code
    kill <pid>


    beendet.

    Im Folgenden Beispiel (von meigrafd) wird gewährleistet, dass der Block in der Schleife nach Eintreffen des SIGTERM noch ungestört abgearbeitet wird. Erst nach Abarbeitung wird das Programm beendet.

    [code=php]
    import signal
    import sys
    import time

    stop = False

    def signal_handler(signal, frame):
    global stop
    print "Beenden wird in die Wege geleitet."
    stop = True

    signal.signal(signal.SIGTERM, signal_handler)

    while not stop:
    time.sleep(1)
    print "ein Durchlauf"

    print "Ende"
    [/php]

    Soll nur die Ausführung bestimmter Befehle vor Beenden gewährleistet werden, so kann man sich an folgendem Beispiel (von meigrafd) orientieren:

    [code=php]
    import signal
    import sys
    import time

    def signal_handler(signal, frame):
    print "Script wird beendet."
    sys.exit(0)

    signal.signal(signal.SIGTERM, signal_handler)

    while True:
    time.sleep(1)
    print "ein Durchlauf"
    [/php]

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!