Multithreading Locks

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
patchef
User
Beiträge: 4
Registriert: Montag 1. März 2021, 10:34

Ich weiß, dass es eigentlich kein schwieriges Thema ist und ich dachte auch ich hätte es verstanden, aber beim rumprobieren habe ich dann doch Zweifel an meinem Verständnis des Themas bekommen...

Also ich habe eigentlich gedacht, dass (sofern kein .join() benutzt wird) alle Threads gleichzeitig ablaufen, und sobald in einem Thread lockMe.acquire aufgerufen wird (Falls lockMe so definiert), pausieren alle anderen Threads bis zum lockMe.release.

Aber ist das wirklich richtig so? Ich habe mal folgenden Code ausprobiert:

Code: Alles auswählen

import threading
import time


class MyFred(threading.Thread):
    def __init__(self, iD):
        threading.Thread.__init__(self)
        self.iD = iD

    def run(self):
        print("Starte ", self.iD)
        lockMe.acquire()
        print("Hier wird Gelockt")
        time.sleep(self.iD * 4)
        lockMe.release()
        print("Beende ", self.iD)


lockMe = threading.Lock()

t1 = MyFred(1)
t2 = MyFred(2)

t1.start()
time.sleep(2)
t2.start()

time.sleep(2)
print("Beende Main Fred")
Und als Ergebnis erhalte ich:

Starte 1
Hier wird Gelockt
Starte 2
Beende 1
Hier wird Gelockt
Beende Main Fred
Beende 2

Wie kann das sein? Ich habe extra im Mainthread noch 2 Sekunden gewartet, und dennoch erscheint "Beende 2" erst nachdem der Mainthread durch ist. Daraus schließe ich, dass der Mainthread schon während dem Lock weitergelaufen ist... Also stimmt irgendwas in meinem allgemeinen Verständnis von Locke nicht. Wäre nett wenn mich jemand aufklärt!

PS: Bin noch blutiger Anfänger, also wenns geht nicht in übermäßiger Fachsprache ;)
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Natuerlich ist der weitergelaufen. Warum sollte er denn deiner Meinung nach nicht weiterlaufen? Nur weil du zufaellig in einem Therad (hier: Main) ein Lock angelegt hast, hat das keinerlei Einfluss auf dessen Ausfuehrung. Dazu musst du schon versuchen, das in dem Thread zu bekommen.

Zwei Anmerkungen:

- gewoehn dir sofort ab, globale Variablen zu benutzen. Auch und gerade wenn du mit Threads arbeitest! Die Fehlerquellen, die du dir damit einhandelst, kannst du noch gar nicht ueberreissen. In diesem Fall ist die Loesung auch noch trivial: einfach das Lock anlegen, und als Argument an MyFred uebergeben.
- das Lock sollte man mit dem with-statement holen, um zu verhindern, dass man es bei einer Exception zB nicht loslaesst:

Code: Alles auswählen

with self._lock:
     ...
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Das Lock blockiert doch nur den zweiten Thread, weil der wartet bis der erste Thread fertig ist. Das Hauptprogramm läuft ganz normal weiter.
Locks immer mit with-Statement benutzen.
Variablennamen schreibt man komplett klein. Warum ein großes D in iD?
Von Thread leitet man normalerweise nicht ab, die Klasse ist überflüssig, da man Thread auch mit einer ganz normalen Funktion nutzen kann.
Benutze keine globalen Varialben: `lock` muß auch ein Argument sein!

Code: Alles auswählen

import threading
import time

def do_fred(lock, id):
    print("Starte ", id)
    with lock:
        print("Hier wird Gelockt")
        time.sleep(id * 4)
    print("Beende ", id)

def main():
    lock = threading.Lock()
    t1 = threading.Thread(target=do_fred, args=(lock, 1))
    t2 = threading.Thread(target=do_fred, args=(lock, 2))
    t1.start()
    time.sleep(2)
    t2.start()
    time.sleep(2)
    print("Beende Main Fred")
Thants
User
Beiträge: 34
Registriert: Dienstag 1. Dezember 2020, 12:00

Hier nochmal zu deiner eigentlichen Frage. Deine folgende Annahme stimmt so nicht ganz:
und sobald in einem Thread lockMe.acquire aufgerufen wird ([...]), pausieren alle anderen Threads bis zum lockMe.release
Der Aufruf von acquire() hält nicht automatisch alle anderen Threads an, sondern nur die, die auch acquire() (am gleichen Lock-Objekt) aufgerufen haben.

Stell dir das Lock-Objekt wie eine Bahnschranke vor. Wenn die Schranke offen ist, kann der erste, der an ihr ankommt durch und macht die Schranke hinter sich zu (ok, die Analogie passt hier nicht ganz, es ist eben eine Bahnschranke ohne Bahn ;)). Alle anderen, die auch noch an der Bahnschranke stehen oder die erst ankommen, müssen warten. Wer aber gar nicht an der Schranke ist, sondern meinetwegen im Supermarkt einkaufen, kann das auch weiterhin tun und muss jetzt nicht alles stehen und liegen lassen. Wenn derjenige der die Schranke geschlossen hat fertig ist, macht er die Schranke wieder auf und der nächste darf durch, der dann die Schranke gleich wieder schließt, usw.

Es ist also tatsächlich nur der Aufruf von acquire(), der entweder sofort zurückkehrt, so dass der aufrufende Thread weiterlaufen kann oder der eben nicht sofort zurückkehrt und damit den aufrufenden Thread blockiert. Diejenigen Threads, die acquire() aber gar nicht aufrufen sind von der ganzen Sache überhaupt nicht betroffen und laufen weiter.
Antworten