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

Dienstag 22. Mai 2007, 08:08

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

Dienstag 22. Mai 2007, 08:34

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

Dienstag 22. Mai 2007, 11:09

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

Dienstag 22. Mai 2007, 13:47

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

Dienstag 22. Mai 2007, 14:57

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

Dienstag 22. Mai 2007, 15:06

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

Sonntag 27. Mai 2007, 10:33

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

Sonntag 27. Mai 2007, 12:33

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:

Sonntag 27. Mai 2007, 13:33

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: 4871
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Sonntag 27. Mai 2007, 18:53

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

Mittwoch 30. Mai 2007, 23:52

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
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Donnerstag 31. Mai 2007, 00:15

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 Modvoice
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Donnerstag 31. Mai 2007, 08:22

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:

Donnerstag 31. Mai 2007, 08:40

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

Donnerstag 31. Mai 2007, 08: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.
Antworten