thread / threading mit Aktualisierung Globaler Variabeln

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.
BlackJack

@cyberchris: Das ist ja gerade der Punkt das immer nur einer Deiner `Timer`-Threads laufen kann. Darum wäre es ja auch sinnvoller sich genau diesen einen gezielt zu merken statt alle zu durchsuchen die gerade laufen.
Sirius3
User
Beiträge: 17745
Registriert: Sonntag 21. Oktober 2012, 17:20

@cyberchris: das mit dem passenden Thread suchen ist nur kosmetisch. Der andere Punkt ist viel schlimmer, weil er wahrscheinlich fast nie auftritt, sich nicht reproduzieren läßt, aber unter Umständen, je nachdem, was Du beim Switchen alles machst, katastrophale Folgen haben kann. Beim nebenläufigen Programmieren muß man wirklich theoretisch alle Möglichkeiten durchdenken und bewiesenermaßen sicher sein, weil sich Fehler, die sich durch Synchronisationsungenauigkeiten ergeben, nicht testen lassen: Es kommt ein Signal; exakt 15s später kurz nachdem der Timer ausgelöst hat, wird die switch_off-Funktion unterbrochen, weil es kommt das nächste Signal, switch_on wird durchlaufen; erst jetzt wird switch_off fortgeführt, und Du stehst im Dunkeln.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Es hat mir keine Ruhe gelassen, dass mir die bisher präsentierten Lösungen nicht gefallen haben. Irgendwie fand ich, dass sie alle zu wenig das Problem explizit machen, das gelöst werden soll. Das Problem wurde bisher auch nur recht unterspezifiziert dargelegt. Wenn es mein Problem wäre, würde ich es vielleicht so formulieren:

Prüfe alle ... Sekunden, ob eine Bewegung festgestellt werden kann. Sobald eine Bewegung festgestellt wurde, schalte das Display an. Wenn bei angeschaltetem Display wenigstens ... Sekunden lang keine Bewegung mehr festgestellt werden konnte, schalte das Display wieder aus.

Das kann man so in Code gießen:

Code: Alles auswählen

#!/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import contextlib
import threading
import time

SENSOR = 15
SENSOR_CHECK_INTERVAL_TIME = 5.0
DELAY_TIME_AFTER_LAST_MOVEMENT = 8.0

@contextlib.contextmanager
def switch():
    print('switch on')
    try:
        yield
    finally:
        print('switch off')

@contextlib.contextmanager
def gpio_context():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(SENSOR, GPIO.IN)
    try:
        yield
    finally:
        GPIO.cleanup()

def movement_detected():
    return GPIO.input(SENSOR) == GPIO.HIGH

def detect(motion_event):
    while True:
        if movement_detected():
            motion_event.set()
        time.sleep(SENSOR_CHECK_INTERVAL_TIME)

def switch_when(motion_event):
    while True:
        motion_event.wait()
        with switch():
            while True:
                motion_event.clear()
                if not motion_event.wait(DELAY_TIME_AFTER_LAST_MOVEMENT):
                    break

def main():
    try:
        with gpio_context():
            motion_event = threading.Event()
            switcher = threading.Thread(target=lambda:switch_when(motion_event))
            switcher.daemon = True
            switcher.start()
            detect(motion_event)
    except KeyboardInterrupt:
        pass

if __name__ == '__main__':
    main()
Statt einer Queue kann man einfacher ein Event Objekt verwenden. Der Hauptthread fungiert als Event-Quelle und der Nebenthread als Event-Senke, d.h. das Event wird ausschließlich im Hauptthread gesetzt und im Nebenthread verwendet und wieder gelöscht. Dort wird zuerst auf eine Bewegung gewartet, worauf das Display sofort angeschaltet wird, und anschließend wird in einer Endlosschleife der Event solange immer wieder zurückgesetzt, wie eine weitere Bewegung innerhalb einer festgelegten Wartezeit festgestellt werden kann. Nur wenn innerhalb dieses Zeitrahmens keine Bewegung mehr festgestellt wurde wird die Schleife verlassen und das Display wieder ausgeschaltet.

Sowohl das GPIO-Initialisieren und -Aufräumen als auch das An- und Ausschalten des Displays folgen dem Muster, für das Contextmanager erfunden wurden.
In specifications, Murphy's Law supersedes Ohm's.
cyberchris
User
Beiträge: 7
Registriert: Samstag 3. Januar 2015, 21:06

Hallo zusammen,

nun der Reihe nach ;-)
@BlackJack: Sicher das wäre durchaus ein Punkt den ich in dem Scipt noch verbessern könnte. Das ist auch das was ich meinte, das mein Script bestimmt nicht die vollkommenste Lösung ist. Eventuell könnte das aber auch, wen man den Punkt den Sirius anführt, ein Problem werden das die Zeitspanne des Überlappens sich vergrößert und dadurch ein problem verusacht.
@Sirius3: Ich denke eine 100% Fehler freie Lösung wird es bestimmt nie geben - Für mich ist es auch wichtig den Aufwand in Relation zur Wahrscheinlichkeit das solche möglichkeit auftritt zu setzen. Ich denke dass die Zeit zwischen dem durchsuchen und dem Cancel des Timer-Threads so verschwindent gering ist das man das durchaus vernachlässigen kann. Im dunkeln werde ich nicht stehen da ja die funktion switch_off in der Funktion edge_detect mit switch_on inbegriffen ist.
@pillmuncher: wen du dir mal das Script nach deiner Formulierung anschaust könnte man feststellen das das durchaus erfüllt ist.
Ich prüfe (sogar über ein event) alle 100ms ob eine Bewegung festgestellt wird. Sobald eine Bewegung festgestellt wurde wird die Funktion edge_detect mit switch_on aufgerufen und das Display eingeschaltet. Dieses befindet sich immer 15s nach der letzten Bewegung in Funktion und wird dan erst über die Timer-Thread beendet.(das wird durch die zeilen 11 bis 16 sicher gestellt)
Wie schon erwäht stehe neuen offen gegenüber und werde mir dein Code noch ausführlich anschauen. Da ich aber mit der contextlib keine Erfahrung habe werde ich vorerst nicht damit arbeiten.
Vielleicht hast du ja für mich ein paar nüzliche links oder auch eine Buch Empfehlung damit ich mit der contextlib was anfangen kann.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@cyberchris: Oft findet man derartigen Code:

Code: Alles auswählen

f = open(my_file)
try:
    ...
finally:
    f.close()

cursor = Database.connect(db_name, my_name, my_password)
try:
    ...
finally:
    cursor.close()

lock.acquire()
try:
    ...
finally:
    lock.release()

some_device.setup()
try:
    ...
finally:
    some_device.cleanup()

switch_on()
try:
    ...
finally:
    switch_off()
Das ContextManager-Protokoll standardisiert dieses Pattern zu:

Code: Alles auswählen

with open(my_file) as f:
    ...

with Database.connect(db_name, my_name, my_password) as cursor:
    ...

with lock:
    ...

with some_device:
    ...

with switch():
    ...
Die einfachste Art, solch einen ContextManager zu erstellen ist:

Code: Alles auswählen

import contextlib

@contextlib.contextmanager
def some_context():
    # initialization code here
    try:
        yield
    finally:
        # cleanup code here
Verwendet wird das dann so:

Code: Alles auswählen

with some_context():
    ...
. Dabei wird some_context() bis zum yield ausgeführt, die Ausführung unterbrochen, ... ausgeführt, und am Ende des Blocks wird some_context() nach dem yield weiter ausgeführt bis zum Ende.

Hier die Doku zu contextlib: https://docs.python.org/2/library/contextlib.html
Hier der PEP der das with Statement definiert: https://www.python.org/dev/peps/pep-0343/
Ansonsten google einfach mal nach python "context manager", da sollte einiges an Information zusammenkommen.
In specifications, Murphy's Law supersedes Ohm's.
Sirius3
User
Beiträge: 17745
Registriert: Sonntag 21. Oktober 2012, 17:20

@cyberchris: bei dem Beispiel ist es vielleicht wirklich nicht so schlimm, weil maximal ein Display durchbrennen kann, oder falls Du das an Deine Garagentorsteuerung anschließt, Du das Garagentor auf den Kopf kriegst. Wenn Du Dir pillmunchers Lösung anschaust, dann ist das Problem da sehr elegant gelöst, dass jede Komponente garantiert immer nur in einem Thread läuft.
Antworten