Ein Kontextmanager für threadsichere Operationen gewährleistet, dass die Sperrung einer Ressource (z. B. einer Datei oder gemeinsamen Daten) immer korrekt freigegeben wird, selbst wenn eine Ausnahme auftritt. Das ist wichtig für multithreaded Programme, in denen mehrere Threads gleichzeitig auf dieselben Daten zugreifen können.
Geschichte der Frage:
Der Bedarf an Threadsicherheit entstand mit der Einführung von Multithreading-Berechnungen. In Python wurde mit Version 2.5 das standardisierte Protokoll der Kontextmanager eingeführt, das die Arbeit mit Ressourcen vereinheitlicht. Für Threads bedeutet dies eine einfache und zuverlässige Verwaltung von Sperren.
Problem:
Bei der manuellen Verwaltung von Sperren (acquire()/release()) kann es leicht passieren, dass man das release vergisst, insbesondere beim Abfangen von Ausnahmen. Dies führt zu Deadlocks. Der Kontextmanager hilft, solche Fehler zu vermeiden.
Lösung:
Verwenden Sie die Standardkontextmanager - entweder durch die integrierte threading.Lock oder indem Sie Ihren eigenen mit Hilfe der magischen Methoden __enter__ und __exit__ implementieren.
Beispielcode:
import threading lock = threading.Lock() # Standardmethode with lock: # Kritischer Abschnitt print("Führt eine threadsichere Operation aus") # Eigener Kontextmanager class SafeLock: def __init__(self, lock): self.lock = lock def __enter__(self): self.lock.acquire() return self def __exit__(self, exc_type, exc_val, exc_tb): self.lock.release() with SafeLock(lock): print("Der individuelle Lock-Manager funktioniert!")
Wesentliche Merkmale:
Muss man beide Methoden (enter, exit) implementieren, damit die Klasse ein Kontextmanager ist?
Nein, es genügt, nur __exit__ zu implementieren, damit die Klasse in der with-Anweisung funktioniert, doch für die Verwendung des internen Zustands ist in der Regel auch __enter__ erforderlich. Ohne __enter__ kann man jedoch kein Objekt/Ressource über as zurückgeben. Daher sind beide Methoden erforderlich, um die Syntax von with vollständig zu unterstützen.
Muss man release explizit in finally aufrufen, wenn man mit lock verwendet?
Nein, mit der with-Anweisung ist das nicht notwendig: release wird automatisch beim Verlassen des Blocks aufgerufen, selbst wenn eine Ausnahme auftritt. Dies ist der Hauptvorteil von Kontextmanagern.
Kann man dasselbe lock gleichzeitig in verschiedenen with-Anweisungen in mehreren Threads verwenden?
Ja, das ist gemäß der Logik von lock vorgesehen: Wenn ein anderer Thread das lock hat, wird der aktuelle Thread blockiert, bis die Ressource freigegeben wird. Eine falsche Organisation der kritischen Abschnitte kann jedoch zu Deadlocks führen, wenn die Reihenfolge des Erwerbs an verschiedenen Stellen im Code unterschiedlich ist.
Ein Entwickler schreibt manuell:
lock.acquire() # Kritischer Abschnitt if etwas_geht_schief: return # release vergessen! lock.release()
Vorteile:
Nachteile:
Verwendung von with:
with lock: # Kritischer Abschnitt if etwas_geht_schief: return
Vorteile:
Nachteile: