Sperren eines spezifischen Objekts in Python, zur Vermeidung von Race Conditions

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.
Antworten
r3b3l
User
Beiträge: 2
Registriert: Sonntag 22. Mai 2016, 13:15

Hallo zusammen,
Ich versuche eine API für Roboter in Python zu programmieren. Jeder Roboter kann eine Menge von Motor Objekten erzeugen, welche in einer Klasse, Motor, implementiert sind. Zum agieren beinhalten sie die Funktion moveMotor, welche in einem Thread ausgeführt werden soll:

Code: Alles auswählen

""" Motor Class """
class Motor:

    def __init__(self, motor_id):
        """
        @param motor_id: Motor Identity
        @type motor_id: int
        """
        self.__motor_id = motor_id
        self.__motorState = "r"

        def moveMotor(self):
            thread = MotorThread(self)
            thread.start()

        def run(self):      
        print("Motor starts...")
        i = 0
        while(i < 3):
            print("Wrrrmmmmmmmmmm")
            time.sleep(1)
            i+=1
Mein Problem ist, dass ich keine Möglichkeit finde, einen Lock oder eine Semaphore zu programmieren, die den Thread nur dann ausführen, wenn ein Motor nicht aktiv ist. Also wenn Motor 1 aktiv ist, kann Motor 2 ausgeführt werden, aber nicht Motor 1.

Ich habe es mit lock = threading.Lock() und lock = threading.Semaphore() versucht und acquire und release an nahezu jeder Stelle, die mir sinnvoll erschien, ausprobiert, aber es nützt nichts.

Ich hoffe jemand kann mir helfen.
Zuletzt geändert von Anonymous am Sonntag 22. Mai 2016, 14:54, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
r3b3l
User
Beiträge: 2
Registriert: Sonntag 22. Mai 2016, 13:15

Egal. Hat sich gelöst. Hab ne neue Variable für die Motorzustand eingeführt und anhand der mit Hilfe meines netten Freunds, des thread joins, den Threadaufruf beschränkt.

Code: Alles auswählen

def moveMotor(self):
		i = 1
		while(i == 1):
			if(self.__motorState == "r"):
				self.__motorState = "a"
				thread = MotorThread(self)
				thread.start()
				thread.join()
				self.__motorState = "r"
				break
Falls jemand Verbesserungsvorschläge hat, nehme ich die als Python Anfänger gerne an.
Zuletzt geändert von Anonymous am Sonntag 22. Mai 2016, 14:55, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@r3b3l: wenn man einen Thread startet und gleich joint, kann man den Thread auch gleich weglassen. So ganz verstanden habe ich noch nicht, was Du versucht hast und erreichen willst. Ein lauffähiges Beispiel ist da immer ganz hilfreich.

Weder if noch while sind Funktionen, haben daher auch keine Argumente, die man in Klammern einschließen müßte. Statt Doppelunterstrichen in den Attributnamen möchtest Du wahrscheinlich maximal einen Unterstrich verwenden.
BlackJack

@r3b3l: Zusätzlich Anmerkungen zu dem was Sirius3 erwähnte: Die Dokumentation für die Klasse steht an der falschen Stelle — *nach* der ``class``-Zeile ist der Platz für einen DocString, nicht davor. Allerdings ist die ”Dokumentation” sinnfrei. Das es sich um eine Klasse handelt und das die Motor heisst, muss man nicht dokumentieren, dass steht da bereits als ``class Motor`` sehr deutlich im Code.

Neben den zu vielen Unterstrichen halten sich die Namen auch nicht alle an den Style Guide for Python Code. Das sollte `move_motor()` und `_motor_state` heissen. Wobei man bei den Attributen auch das Motor jeweils weglassen sollte, denn die Klasse heisst ja schon `Motor`. Das jetzt auch noch in so ziemlich jedem Attribut noch mal extra zu erwähnen ist redundant.

Für `Motor._state` solltest Du etwas sprechenderes als einbuchstabige Zeichenketten verwenden. Und dann auch als Konstanten definiert, oder als `enum`, damit es sicherer auffällt, wenn man da etwas schreibt was es nicht gibt oder geben sollte. Eventuell kann man an der Stelle auch ein bisschen mehr OOP betreiben und den Zustand in aktive Objekte umwandeln.

Die ``while``-Schleife im ersten Beispiel wäre besser eine ``for``-Schleife.

Bei der ``while``-Schleife im zweiten Beispiel ist `i` total überflüssig. Das kann man einfacher mit einer ”Endlosschleife” ausdrücken, also ``while True:``. Denn zu `True` wird ``i == 1`` ja in dem Code grundsätzlich *immer* ausgewertet. Warum also mit einer überflüssigen Variable immer wieder diesen Konstanten Wert berechnen?

Code: Alles auswählen

class Motor(object):

    ROTATING = 'rotating'
    ASPHYXIATING = 'asphyxiating'

    # ...

    def move(self):
        while True:
            if self._state == self.ROTATING:
                self._state = self.ASPHYXIATING
                # 
                # Look Ma, no Thread here.
                # 
                do_whatever_motor_threads_run_method_would_have_done()
                self._state = self.ROTATING
                break
Falls Dir die Bezeichner für die Zustandskonstanten komisch vorkommen, sowas passiert im Extremfall wenn man kryptische Abkürzungen verwendet und die Leser anfangen müssen zu raten. ;-)

Falls es wichtig sein sollte, das das Zustandsattribut nach der `move()`-Methode sicher wieder auf `ROTATING` steht, sollte man da ein ``try``/``finally`` einbauen.

Falls die `move()`-Methode von verschiedenen Threads aus aufrufbar sein soll, dann gehört *dort* eine Sperre oder eine ähnliche Massnahme rein. Diese ``if``-Abfrage mit dem setzen auf einen neuen Wert ist nicht atomar und damit auch nicht threadsicher.

Programmieren an sich funktioniert übrigens nicht in dem man etwas versucht an allen möglichen Stelle einzufügen bis es irgendwann mal klappt, und nebenläufige Programmierung funktioniert so schon mal gar nicht. Dort ”klappt” so einiges, aber eben nur solange bis es irgendwann einmal nicht funktioniert. Und zwar nicht deterministisch, sondern mal funktioniert es und mal nicht, je nachdem wie die Abläufe in den Threads sich gerade entwickeln. Das lässt sich also nicht einfach so durch ausprobieren testen und man kann nicht sagen man hat die richtige Stelle oder Kombination von Anweisungen gefunden, nur weil es ein paar mal problemlos gelaufen ist.
Antworten