Threading Frage

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
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Hallo,

als ich mir Gerolds Tutorial durchgelesen habe, hat sich mir die Frage in den Weg gestellt, was die Klasse Lock im Modul Threading bringt.
Ich kann mir darunter nicht so richtig etwas vorstellen.
Ich habe ein bisschen mit der interaktiven Shell experimentiert, die Doku gelesen - is bei Lock in bisschen kurz geraten =D und auch im Netz nach Python Threading Tutorials gesucht, bin aber nicht wirklich fündig geworden - oder war mal wieder zu dämlich zu suchen.
Ich würd mich über (deutsche?) Links und andere Erklärungen freuen.
LG
Costi
User
Beiträge: 545
Registriert: Donnerstag 17. August 2006, 14:21

Lock hat zwei methoden, ``release`` und ``acquire``

wenn ein lock acquired wurde, wird ein naechster acquire aufruf solange blocken, bis der Lock released wurde

das ist nuetzlich, wenn zb ein thread auf eine resource vom anderen thread will.
der eine thread released erst, wenn die recource fertig zum weitergeben ist. und der andere thread hat einen aquire vor der verwendung der recoursse

ich hoffe du hasts verstanden
cp != mv
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Danke, aber leider hab ich es nicht ganz verstanden.
Wo liegt der Unterschied zwischen blockierenden Locks und nicht blockierenden?
Und woher weiß der eine Thread, dass ein Codeabschnitt gelocked ist?

Code: Alles auswählen

        # Versucht einen nicht blockierenden Lock zu bekommen
        for i in xrange(self.max_lock_tries):
            if self._lock.acquire(False):
                break
            time.sleep(0.1)
        else:
            raise cherrypy.TimeoutError()
        try:
            if filename in self:
                template = dict.__getitem__(self, filename)
                # Prüfen ob sich das Template im Dateisystem geändert hat
                if template._mtime == os.path.getmtime(filename):
                    return template
                else:
                    # Alten Dateinamen aus Liste entfernen, neu laden und
                    # zurück geben
                    try:
                        self._filenames_list.remove(filename)
                    except ValueError:
                        pass
                    return self.load_templatefile(filename)
            else:
                # Template laden, in dict eintragen und zurück geben
                return self.load_templatefile(filename)
        finally:
            self._lock.release()
Wenn ich am Code von Gerolds Tutorial versuch das "Verhalten" von Lock etc zu verstehen misslingt mir das total. :oops:
LG
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

CrackPod hat geschrieben:hat sich mir die Frage in den Weg gestellt, was die Klasse Lock im Modul Threading bringt.
Hallo CrackPod!

Das ist keine Klasse, sondern eine "Factory Function". Diese Funktion liefert ein Lock-Objekt zurück. Dieses Lock-Objekt hat zwei wichtige Methoden: ``acquire()`` und ``release()``

Acquirieren mehrere Threads hintereinander diesen Lock, dann bekommt der erste Thread das OK-Signal und startet mit seiner Arbeit. Alle anderen Threads bleiben an der Stelle, an der der Lock angefordert wurde stehen.

Nachdem der erste Thread mit seiner Arbeit fertig ist, gibt dieser den Lock wieder frei. Jetzt ist der nächste Thread mit der Arbeit dran. Und wenn der nächste Thread mit seiner Arbeit fertig ist, gibt dieser den Lock wieder frei. Somit kann der nächste Thread seine Arbeit aufnehmen. Usw...

Mit Hilfe eines Locks kann man also mehrere Threads dazu zwingen, eine bestimmte Arbeit NICHT gleichzeitig zu tun.
Jeder Thread der den gleichen Lock anfordert, muss so lange warten, bis alle Threads die den Lock vorher angefordert haben, mit deren Arbeit fertig sind.

Ich möchte zum Beispiel nicht, dass Vorlagen entsorgt werden (_autovacuum), wenn ein anderer Thread gerade eine Vorlage anfordert (__getitem__). Es könnte sonst passieren, dass ``_autovacuum`` die Vorlage raus schmeißt, während diese gerade von ``__getitem__`` neu eingelesen wird (weil diese sich evt. im Dateisystem geändert hat).

Oder, was auch passieren könnte: ``__getitem__`` hat soeben geprüft, ob die Vorlage im Dictionary ist. Ja, es ist im Dictionary. Im nächsten Schritt möchte ``__getitem__`` die Vorlage aus dem Dictionary auslesen. Aber die Vorlage ist nicht mehr da! ``_autovacuum`` hat die Vorlage genau zwischen der Prüfung und dem Auslesen entsorgt. Das Programm würde mit einem Fehler abbrechen. -- Ein Lock verhindert solche Situationen.

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 CrackPod!
CrackPod hat geschrieben:Wo liegt der Unterschied zwischen blockierenden Locks und nicht blockierenden?
Acquirieren mehrere Threads hintereinander diesen Lock, dann bekommt der erste Thread das OK-Signal und startet mit seiner Arbeit. Alle anderen Threads bleiben an der Stelle, an der der Lock angefordert wurde stehen.
Das ist das Verhalten eines blockierenden Locks. Alle Threads die diesen Lock anfordern, bleiben hier stehen. Egal wie lange. Kein Timeout!

Wenn ich jetzt nicht möchte, dass ein Lock alles blockieren kann, dann fordere ich einen Lock an der nicht blockiert. Beim Anfordern wird geprüft, ob ein anderer Thread diesen Lock angefordert und noch nicht frei gegeben hat. Ist das der Fall, dann liefert ``lock.acquire(False)`` False zurück und wartet nicht bis der Lock vom anderen Thread wieder frei gegeben wurde.

Es gibt kein Timeout bei dieser Anfrage. Entweder ein anderer Thread blockiert, oder nicht. Es gibt nur ein True, der Lock gehört jetzt dir oder ein False, der Lock gehört einem anderen Thread.

Um z.B. 30 sec. lang zu versuchen, einen Lock zu bekommen, muss man also ``lock.acquire(False) so oft hintereinander ausführen, bis man entweder ein True zurück bekommt oder einem die Geduld ausgeht (z.B. 30 sec.). Und damit das Anfordern des Locks in der Schleife die CPU nicht voll auslastet, wird zwischen jedem Versuch ein bischen gewartet.

Code: Alles auswählen

        # Versucht einen nicht blockierenden Lock zu bekommen.
        # So lange, bis True zurück geliefert wird oder die Schleife
        # 300 Mal durchlaufen wurde. 
        for i in xrange(self.max_lock_tries):
            if self._lock.acquire(False):
                break
            time.sleep(0.1)
        else:
            # Wird die Schleife alle 300 Mal durchlaufen, ohne vorher
            # ``break`` auszulösen, dann wird dieser Codeabschnitt ausgeführt.
            # Wird ``break`` ausgelöst, also erfolgreich ein Lock angefordert,
            # dann wird dieser Block nie ausgeführt.
            raise cherrypy.TimeoutError()
        
        # Wenn dieser Punkt erreicht wurde, dann wurde der Lock erfolgreich
        # angefordert. Wenn nicht, dann wäre Python nicht bis hier her gekommen.
        try:
            # Hier wird gearbeitet...
            # Hier wird gearbeitet...
            # Hier wird gearbeitet...
        finally:
            # Auch wenn es beim Arbeiten im "try"-Block zu einem Fehler kommen würde --
            # dieser Block wird immer ausgeführt. Warum ist das so wichtig?
            # Wenn der Lock nicht wieder frei gegeben wird, dann kann kein
            # anderer Thread mehr diesen Lock erfolgreich anfordern.
            self._lock.release()
CrackPod hat geschrieben:Und woher weiß der eine Thread, dass ein Codeabschnitt gelocked ist?
Da alle Threads den gleichen Lock anfordern, kann dieser eine Lock die Threads gezielt blockieren. ``acquire()`` ist wie eine Funktion, von der erst dann zurück gekehrt wird, wenn die Zeit reif dafür ist. --> Die Threads bleiben so lange beim ``acquire()`` stehen, bis diese Funktion fertig abgearbeitet wurde. Wie lange das dauert, bestimmt der Lock.

mfg
Gerold
:-)
Zuletzt geändert von gerold am Sonntag 10. Juni 2007, 22:50, insgesamt 1-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Das wirft mir aber nochmal neue Fragen auf

-Woher weiß CheetahTemplateContainer._autovacuum, wenn Root.default einen Lock auf ein Template hat? Beide Klassen verwenden schlieslich andere Variablen, um den Zustand zu speichern.(Wenn es das nicht weiß, auf welche Art wird dann das eventuelle Löschen des Templates verhindert?)

-Was ist eine "Factory Function"?
Nach der Befragung war ich nicht sonderlich schlauer. Außer dass man sowas auch anonyme Funktion nennt(?) weiß ich nicht mehr =D Bzw ich habe es nicht verstanden :?

-Welchen unterschied haben blockierende und nicht blockierende Locks?
LG
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo CrackPod!
CrackPod hat geschrieben:Woher weiß CheetahTemplateContainer._autovacuum, wenn Root.default einen Lock auf ein Template hat? Beide Klassen verwenden schlieslich andere Variablen, um den Zustand zu speichern.(Wenn es das nicht weiß, auf welche Art wird dann das eventuelle Löschen des Templates verhindert?)
Jetzt weiß ich wo dein Problem ist. :D
Ganz einfach --> beide Locks haben nichts miteinander zu tun. Beide wissen nichts von dem anderen Lock.

Der Lock im ``CheetahTemplateContainer`` kümmert sich nur darum, dass sich ``__getitem__`` und ``_autovacuum`` nicht in die Quere kommen.

Der Lock in ``Root`` kümmert sich darum, dass die Vorlagen nicht gleichzeitig angefordert und gerendert (also mit Daten befüllt) werden. Um weniger oft zu blockieren, könnte man im ``Root`` nur dann blockieren, wenn die gleiche Vorlage mehrmals gleichzeitig angefordert wird. Das würde aber das Programm noch mehr verkomplizieren, als es schon ist. Das könnte man dann tun, wenn wirklich viele, viele, viele, viele Leute gleichzeitig auf die Seiten zugreifen.
CrackPod hat geschrieben:Was ist eine "Factory Function"?
Eine Factory Function (Fabrik Funktion) erzeugt ein Objekt und gibt dieses zurück. Wenn dir z.B. die Funktion ``gib_mir_eine_klasseninstanz`` eine fertig initialisierte Klasseninstanz zurück gibt, dann ist diese Funktion eine "Factory Function". Es gibt Objekte, die sich nicht direkt instantiieren lassen. Das ist z.B. ab und zu mal der Fall, wenn direkt auf in C geschriebene Objekte zugegriffen wird.

Code: Alles auswählen

class _Buch(object):
    def __init__(self, sachgebiet):
        self.sachgebiet = sachgebiet
    
def new_computerbuch():
    return _Buch(sachgebiet = "Computer")

python_for_beginners = new_computerbuch()
print python_for_beginners.sachgebiet
Nehmen wir einfach mal an, auf die Klasse ``_Buch`` darf oder soll nicht direkt zugegriffen werden. Aber irgendwie muss man ja doch zu einer Instanz kommen. Diesen Part erledigt jetzt die Factory Function ``new_computerbuch``. Diese Funktion kümmert sich darum, dass ein gültiges Sachgebiet übergeben wird und gibt die fertig initialisierte Klasseninstanz zurück.

Das ist wahrscheinlich nicht besonders "pythonisch", um dieses Wort mal wieder aufzugreifen, aber manche systemnahe Dinge lassen sich nicht schöner erledigen.

``threading.Lock()`` greift eigentlich auf das Lowlevel-Modul ``thread`` zu und reicht den Aufruf an ``thread.allocate_lock`` weiter. Damit wird ein neues Lock-Objekt erstellt.

mfg
Gerold
:-)

Wenn ich da etwas falsch verstanden habe, dann bessert mich bitte aus.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Danke für die umfassenden Antworten, Gerold.
Ich denke, ich hab das ganze gewusel verstanden.
Und es kann deswegen kein Template gelöscht werden, weil man nur über CheetahTempateContainer.__getitem__ rankommt, das einen Lock aufbaut, weswegen _autovacuum nichts mehr machen kann.
Gut, dann denk ich is alles geklärt.
Nur die Sache mit den blockierenden und nicht blockierenden Locks würd mich noch interessieren, aber das scheint irgendwie nicht so wichtig zu sein, sonst wärst du sicher drauf eingegangen^^
LG
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

CrackPod hat geschrieben:Nur die Sache mit den blockierenden und nicht blockierenden Locks würd mich noch interessieren, aber das scheint irgendwie nicht so wichtig zu sein, sonst wärst du sicher drauf eingegangen^^
Hallo CrackPod!

??? Fast der ganze Beitrag http://www.python-forum.de/post-70429.html#70429 widmet sich diesem Thema! Lies ihn lieber noch einmal komplett durch. (inkl. den Quote-Tags am Anfang und dem Quellcodeschnipsel mit den vielen Kommentaren) Ich habe mir viel Mühe damit gegeben.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Ok, ich glaub ich hab das falsch ausgedrückt oder etwas wirklich noch nicht 100%ig verstanden. Es ging mir um folgendes:
Worin liegt der genaue Unterschied bei Lock.acquire(), wenn man True bzw False übergibt? So war die Frage egtl gemeint, scheint aber so nicht rübergekommen zu sein. In der Doku steht man hat einen blocking Lock - also eine blockierenden(schon oder??) Lock bei True und einen non-blocking Lock bei False.
Wo da der genaue Unterschied besteht, wollte ich noch wissen. Sollte das auch in deinem Post steht, dann zerleg ich ihn in so kleine Teile, bis ich rausfind, was ich wissen will =)
Aber schonmal danke für den obrigen Post, ich hab schon einiges verstanden :)
LG
BlackJack

Bei `True` verhält es sich normal, d.h. der Thread wird an der Stelle solange blockiert, bis die Sperre freigegeben wurde und `acquire()` ausgeführt wird.

Bei `False` kehrt der Aufruf sofort zurück und liefert `True` oder `False` als Rückgabewert, je nachdem, ob man die Sperre nun "besitzt" oder nicht.
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Jetz hab ichs gecheckt =)
Bei True wird die Sperre quasi erzwungen und ggf gewartet, bis man sie erhält und bei False wird probiert, ob man die Sperre bekommt, wenn nicht, dann wird False zurückgeliefert, wenn schon, dann True.
Gutgut :)
Jetzt nochmal Tutorial bezogen, damit ich weiß, was ich wann anweden muss =D
In der Methode _autovacuum, muss ein Lock erworben werden, damit nichts gelöscht wird, was gerade verwendet wird, richtig?
In __getitem__ wird so lange versucht, die Sperre zu bekommen, bis ein TimeOut geworfen wird, damit dem User irgendwie gesagt werden kann, dass seine Seite z.Z. nicht erreichbar ist.(Z.B. weil es schon so viele Zugriffe - also Locks? - auf die Seite gibt?)
Selbiges bei Root.default oder?

Wenn das alles so richtig is, hab ich soweit keine Fragen mehr und bedank mich bei eurer tollen Hilfe :D
LG[/code]
Antworten