Threadprogrammierung mit Variablen

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

Hi,
gehen wir davon aus ich hab ein Programm das einen weiteren Thread eröffnent. Der Thread und das Programm laufen beständig weiter müssen aber während der laufzeit daten austauschen. Gibt es hier eine Möglichkeit auf Globale variablen zu verzichten?
Mit return kann man ja nicht arbeiten da sonst der Thread aufhören würde.

Schöne Grüße
Miko
BlackJack

Die sicherste und einfachste Kommunikation kann man über `Queue.Queue()`-Objekte abwickeln.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Interessant sind in diesem Zusammenhang auch die beiden neuen Methoden ``join()`` und ``task_done()``, die mit Python 2.5 hinzugekommen sind.
Miko

danke erstmal für die antworten.

ich will aber nicht dass objekte übergeben werden indem die eine funktion übergibt und die andere empfängt sondern dass nur die funktion die die variable ändert dies irgendwie hinterlegt. geht das mit queue ?
BlackJack

Nicht so direkt, nein. Du kannst natürlich mit einer Funktion beliebige Attribute von Objekten ändern die in beiden Threads bekannt sind, aber dann musst Du auf die ganzen Sachen achten die bei nebenläufiger Programmierung so passieren können, "race conditions", "deadlocks" usw. und da selber Sperren setzen um das zu verhindern. Queues verwenden intern schon die notwendigen Sperrmechanismen, da hat man wenig Sorgen mit.

Was genau willst Du denn machen?
Miko

hm eigentlich nur die gleichzeitige eingabe und den ablauf des programms. das funktioniert mit globalen variablen schon ganz gut.
Miko

Ich hab die lösung ... ich nehm einfach klassenvariablen XD
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Miko hat geschrieben:Ich hab die lösung ... ich nehm einfach klassenvariablen XD
Das funktioniert nicht, ohne diese speziell (bwpw. durch threading.Lock) zu schützen. Da ist Queue.Queue einfacher, sonst hast du die gleichen Probleme, wie bereits in den vorangegangenen Posts erwähnt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

EyDu hat geschrieben:Das funktioniert nicht, ohne diese speziell (bwpw. durch threading.Lock) zu schützen.
Hallo!

Ich habe hier mal ein Beispiel für die Verwendung eines Locks, bin mir aber nicht sicher, ob dieser Aufwand wirklich notwendig ist. Dafür kenne ich mich zu wenig mit den Python-Interna aus.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import threading


class ThreadSafeAttributes(object):
    
    def __init__(self):
        object.__init__(self)
        object.__setattr__(self, "_lock", threading.Lock())

    
    def __getattribute__(self, name):
        lock = object.__getattribute__(self, "_lock")
        lock.acquire()
        attrvalue = object.__getattribute__(self, name)
        lock.release()
        return attrvalue


    def __setattr__(self, name, value):
        lock = object.__getattribute__(self, "_lock")
        lock.acquire()
        object.__setattr__(self, name, value)
        lock.release()


def main():
    """testen"""
    
    import time
    
    def testthreadfunc(tsa):
        tsa.vorname = "Gerold"
        print tsa.vorname
        time.sleep(0.9)
        tsa.vorname = "Martin"
        print tsa.vorname
    
    tsa = ThreadSafeAttributes()
    
    t1 = threading.Thread(target = testthreadfunc, args = (tsa, ))
    t2 = threading.Thread(target = testthreadfunc, args = (tsa, ))
    t3 = threading.Thread(target = testthreadfunc, args = (tsa, ))

    t1.start()
    time.sleep(0.1)
    t2.start()
    time.sleep(0.1)
    t3.start()

    t1.join()
    t2.join()
    t3.join()


if __name__ == "__main__":
    main()
Ich bitte um Kommentare von Leuten, die sich damit auskennen. Ist das OK so? Ist das notwendig? Gibt es eine bessere Lösung?

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

gerold hat geschrieben:Ich habe hier mal ein Beispiel für die Verwendung eines Locks, bin mir aber nicht sicher, ob dieser Aufwand wirklich notwendig ist. Dafür kenne ich mich zu wenig mit den Python-Interna aus.
In den meisten Fällen wird es sicher ohne einen Lock gehen, aber selbst wenn nur bei jedem 1000-sten Durchlauf etwas nicht funktioniert, ist das Programm einfach nicht korrekt.

Das Beispiel ist schon ganz OK so, mann sollte die kritischen Abschnitte nur noch mit einem try-finally umwickeln, da man sich im Fehlerfall, oder gar einer gewollten Exception, sonst sehr leicht einen Deadlock einfängt:

Code: Alles auswählen

    def __getattribute__(self, name):
        lock = object.__getattribute__(self, "_lock")
        lock.acquire()
        try:
            return object.__getattribute__(self, name)
        finally:
            lock.release()


    def __setattr__(self, name, value):
        lock = object.__getattribute__(self, "_lock")
        lock.acquire()
        try:
            object.__setattr__(self, name, value)
        finally:
            lock.release()
Wie man sieht, vereinfacht sich das "__getattribute__" sogar, da man das "return" bereits im try-Block machen kann.

Seit 2.5 gibt es noch die wunderschöne Variante über das with-Statement:

Code: Alles auswählen

    def __getattribute__(self, name):
        with object.__getattribute__(self, "_lock"):
            return object.__getattribute__(self, name)


    def __setattr__(self, name, value):
        with object.__getattribute__(self, "_lock"):
            object.__setattr__(self, name, value)
Miko

Ich hoff ich muss mich jetzt nicht schämen wenn ich das nicht gleich versteh.

Fehler gibt es doch nur wenn 2 threads gleichzeitig probieren eine variable zu ändern oder?
Das dürfte doch theoretisch garnicht möglich sein da der prozessor doch nur eine aufgabe gleichzeitig erledigen kann (dualcores mal ausgeschlossen).
kann es somit doch zu fehlern bei einprozessoresystemen kommen?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Miko hat geschrieben:Fehler gibt es doch nur wenn 2 threads gleichzeitig probieren eine variable zu ändern oder?
Das dürfte doch theoretisch garnicht möglich sein da der prozessor doch nur eine aufgabe gleichzeitig erledigen kann (dualcores mal ausgeschlossen).
kann es somit doch zu fehlern bei einprozessoresystemen kommen?
Die Fehler gibt es, wenn du auf eine Variable zugreifst, stell dir Folgende Situation vor: Du hast zwei Threads, einer führt den nachfolgenden Code aus und einer hängt einen String als letztes Zeichen zu einem bestimmten Zeitpunkt 'b' an die Variable ``var`` an.

Code: Alles auswählen

if var.endswith('a'):
    var = var[0:-2] + 2* var[-1]
Jetzt kann es sein, dass Thread 1 fie If Zeile abgearbeitet wird. Dann kommt Thread 2 und hängt an ``var`` ein 'b' an. Und schon hast du einen Fehler, dass der String mit zwei 'b' statt mit zwei 'a' endet. Sowas nennt sich dann Race Condition und ist ziemlich kompliziert aufzuspüren.

P.S.: Tut mir leid für das blöde Beispiel, ich hoffe es kommt das rüber, was ich ausdrücken will, aber um viertel nach eins ist mir nichts besseres eingefallen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Es reicht auch schon sowas wie

Code: Alles auswählen

a += 1
Was passiert? Der Wert von a wird aus dem Speicher in ein Register gelesen, es wird eins hinzuaddiert, dann wird das Ergebnis wieder im Speicher abgelegt. Obwohl dieser ganze Vorgang in Python nur ein einziges Statement ist, ist das intern keine atomare Operation. Es sind mehrere Arbeitsschritte notwendig, zwischen denen ein Threadwechsel stattfinden kann. Fuehren nun zwei Threads diese Zeile aus, und passiert ein Threadwechsel jeweils genau nach dem Einlesen des Werts von a, dann zaehlen beide Threads "ihr" a hoch, speichern es wieder... wobei ein Hochzaehlen verloren geht.

Egal, ob echte Gleichzeitigkeit auf mehreren CPUs oder scheinbare Gleichzeitigkeit auf einer CPU, die Probleme bleiben die gleichen. Das Dumme ist, dass so ein Fehler nur hoechst selten auch wirklich mal auftritt, und dann steht man dumm da...
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo!

Die Frage ist ja immer noch, ob es überhaupt notwendig ist, allein den Zugriff auf eine gemeinsam genutzte Variable mit einem Lock zu versehen, oder ob es damit nie ein Problem geben kann.

Dass man Algorithmen, die auf eine "gemeinsam genutzte Variable" zugreifen, mit einem Lock absichern sollte ist jetzt sicher klar, aber kann auch einfach nur der Zugriff (lesend oder schreibend) Schwierigkeiten machen? Wahrscheinlich nicht, oder doch?

Wenn nicht, dann wäre die oben von mir entworfene Klasse für den Zugriff auf einfache Variablen (int, str) total umsonst.

Je mehr ich darüber nachdenke, desto mehr glaube ich, dass sich Python intern (Stichwort: GIL) darum kümmert, dass sich Threads beim Zugriff auf eine Variable nicht in die Quere kommen.

Wichtig wäre hier eine Aussage von jemandem, der sich mit dieser Thematik besser auskennt -- also näher an der Entwicklung von Python dran ist.

Edit:
BlackJack hat geschrieben:Du kannst natürlich mit einer Funktion beliebige Attribute von Objekten ändern die in beiden Threads bekannt sind,
Damit wäre das Sperren, bei jedem Zugriff auf eine gemeinsam genutzte Variable sinnlos und man müsste sich nur um logische Fehler im Code kümmern...

mfg
Gerold
:-)
Zuletzt geändert von gerold am Donnerstag 31. Mai 2007, 08:46, insgesamt 1-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Miko hat geschrieben:hm eigentlich nur die gleichzeitige eingabe und den ablauf des programms. das funktioniert mit globalen variablen schon ganz gut.

Pfui, globale variablen als kommunikationsweg....

Warum nicht Queue objekte?

Benutze eben einen thread für die "arbeit" und einen für eingaben.
Dazwischen eine Queue, z.b. "eingabe_queue".
Der worker checkt regelmässig ob in der eingabe queue etwas liegt und reagiert darauf.

Besser als jegliches selbst gefrickeltes.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mad-Marty hat geschrieben:Warum nicht Queue objekte?
Benutze eben einen thread für die "arbeit" und einen für eingaben.
Dazwischen eine Queue, z.b. "eingabe_queue".
Der worker checkt regelmässig ob in der eingabe queue etwas liegt und reagiert darauf.
Hallo Mad-Marty!

Queue-Objekte sind nicht das Allheilmittel. Sie sind super, wenn ich z.B. einen Thread blockieren lassen möchte, bis ein neuer Wert übergeben wird, oder wenn immer wieder verschiedene Werte von einem Hauptthread übergeben werden müssen.

ABER sie bringen nichts, wenn mehrere Threads "gleichzeitig" mit den selben Werten arbeiten müssen. Z.B. ein Counter. oder bei einem Poker-Spiel die Einsätze der Mitspieler. Man könnte den neuen Einsatz zwar in eine Queue legen, aber dann kann nur ein Thread etwas damit anfangen. --> Genau der Thread, der sich den Wert als Erster aus der Queue holt. Man müsste also immer einen Thread haben, der den Wert wieder verteilt.

Ja, es ist natürlich mit Queues machbar, aber die Frage ist, ob es überhaupt notwendig ist. Genügt es vielleicht schon, von einem Thread aus eine Variable zu setzen, auf die die anderen Threads ebenfalls Zugriff haben? Das macht das Programm nicht so kompliziert wie es z.B. mit Queues sein kann.

Alles was ich hier geschrieben habe gilt nur dann, wenn es kein Problem beim "einfachen" Zugriff auf eine "einfache" Variable (z.B. str oder int) geben kann. Das ist für mich aber im Moment noch nicht bestätigt.

Wenn es aber bestätigt wird, dann spricht nichts gegen den gemeinsamen Zugriff auf eine Variable. Dann muss nur noch die Verwendung des "global"-Statements eingeschränkt werden. Z.B. durch Auslagern der gemeinsam benutzten Werte in eine gemeinsam genutzte Klasseninstanz.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo!

Jetzt weiß ich auch wieder, warum ich mir nicht sicher bin, ob der einfache Variablezugriff Schwierigkeiten bereiten kann.

Ich glaube, in folgendem Beispiel gab es Ungereimtheiten (bei der Verwendung mit wxPython), die sich erst legten, als ich die Statusvariable in ein Event umwandelte. (ganz sicher bin ich mir allerdings nicht :oops: )

In diesem Beispiel wird die Methode "stop" von einem anderen Thread aufgerufen.

Code: Alles auswählen

import threading


class HatSchwierigkeitenBereitet(threading.Thread):
    
    def __init__(self):
        threading.Thread.__init__(self)
        self.canceled = False
    
    def run(self):
        while True:
            if self.canceled:
                break
            print "."
    
    def stop(self):
        self.canceled = True


class Funktioniert(threading.Thread):
    
    def __init__(self):
        threading.Thread.__init__(self)
        self.canceled = threading.Event()
    
    def run(self):
        while True:
            if self.canceled.isSet():
                break
            print "."
    
    def stop(self):
        self.canceled.set()


class FunktioniertWahrscheinlichAuch_IstAberEvtNichtNotwendig(threading.Thread):
    
    def __init__(self):
        threading.Thread.__init__(self)
        self.canceled = False
        self.lock = threading.Lock()
    
    def run(self):
        while True:
            self.lock.acquire()
            try:
                if self.canceled:
                    break
            finally:
                self.lock.release()
            print "."
    
    def stop(self):
        self.lock.acquire()
        try:
            self.canceled = True
        finally:
            self.lock.release()
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@gerold

Deine letzten beiden Beispiele sind im Prinzip identisch, du hast lediglich "threading.Event" nachgebaut und quasi zur Weitergabe von beliebitgen Objekten erweitert.

Dein erstes Beispiel wird früher oder später Probleme bereiten. Momentan ist es zwar so, dass eine Anweisung, die direkt in Bytecode umgewandelt werden kann nicht unterbrechbar ist (dafür ist der GIL da), aber das wird sicherlich nicht ewig so bleiben. Wenn ich mich richtig erinnere, meine ich schon von einer inoffiziellen Version gehört zu haben, die diese Beschränkung nicht mehr hat (vielleicht kennt ja jemand den Namen).

In deinem Beispiel hingegen hat man neben der Zuweisung auf self.canceled (was glaube ich in eine einzige Bytecode-Anweisung umgesetzt wird) noch die Auflösung von "self". Das scheint zwar noch nicht kritisch zu sein, aber wenn man jedes mal auf so etwas achten muss, geht bei irgend einer Änderung mit Sicherheit etwas schief.
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Ich haette jetzt vermutet, dass sich die ersten beiden theoretisch gleich verhalten muessten (mal davon abgesehen, dass etwas anderer Code ausgefuehrt wird, sodass sie sich praktisch doch wieder anders verhalten). Es koennte ein Kontextwechsel zwischen der if-Abfrage und dem break auftreten, sodass die anschliessende print-Ausgabe noch ausgefuehrt wird, wenn self.cancel schon auf True steht. Lediglich das letzte Beispiel verhindert dies, da ein Lock um beide Statements gelegt wird.

Ist natuerlich die Frage, was hier "funktioniert (nicht)" bedeutet.
Miko

wobei meistens braucht man ja nicht eine variable in 2 threads zum schreiben.

und wenn ein thread schreibt und einer liest dürfts doch keine probleme geben oder?

Dinge wie lock und queue kenn ich noch nicht deswegen blick ich da noch nicht ganz durch.
Ich benutze auch immer das Modul Thread und nicht threading!

Aber danke für die zahlreichen antworten
Antworten