Een contextmanager voor thread-veilige operaties zorgt ervoor dat de blokkering van een resource (bijvoorbeeld een bestand of gedeelde gegevens) altijd correct wordt vrijgegeven, zelfs bij het optreden van een uitzondering. Dit is belangrijk voor multithreaded programma's, waarbij meerdere threads tegelijkertijd toegang kunnen hebben tot dezelfde gegevens.
Geschiedenis van de vraag:
De behoefte aan thread-veiligheid ontstond met de introductie van multithreaded computation. In Python werd in versie 2.5 het standaardprotocol voor contextmanagers geïntroduceerd, dat het beheer van resources verenigt. Voor threads betekent dit eenvoudig en betrouwbaar beheer van locks.
Probleem:
Bij handmatige beheersing van locks (acquire()/release()) is het gemakkelijk om vergeten te raken de release aan te roepen, vooral bij het afhandelen van uitzonderingen. Dit leidt tot deadlocks. Een contextmanager helpt dergelijke fouten te voorkomen.
Oplossing:
Gebruik standaard contextmanagers - ofwel met behulp van de ingebouwde threading.Lock, of door je eigen te implementeren met behulp van magische methodes __enter__ en __exit__.
Voorbeeldcode:
import threading lock = threading.Lock() # Standaardmethode with lock: # Kritieke sectie print("Voert een thread-veilige operatie uit") # Eigen contextmanager 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("Eigen lock manager werkt!")
Belangrijke kenmerken:
Is het noodzakelijk om beide methoden (enter, exit) te implementeren om een klasse als contextmanager te laten fungeren?
Nee, het is voldoende om alleen __exit__ te implementeren zodat de klasse werkt binnen de with-structuur, maar voor het gebruik van interne status is meestal ook __enter__ nodig. Zonder __enter__ is het niet mogelijk om een object/resource via as terug te geven, dus voor volledige ondersteuning van de with-syntaxis zijn beide methoden vereist.
Moet je release expliciet aanroepen in finally als je met lock werkt?
Nee, met de with-structuur is dat niet nodig: release wordt automatisch aangeroepen bij het verlaten van het blok, zelfs als er een uitzondering optreedt. Dit is het belangrijkste voordeel van contextmanagers.
Kan dezelfde lock tegelijkertijd met verschillende with-statements in meerdere threads worden gebruikt?
Ja, dit is voorzien in de logica van de lock: wanneer geprobeerd wordt een lock die door een andere thread is vastgehouden te verkrijgen, zal de huidige thread worden geblokkeerd totdat de resource is vrijgegeven. Een onjuiste organisatie van kritieke secties kan echter leiden tot deadlocks als de volgorde van vastleggen anders is op verschillende plaatsen in de code.
Een ontwikkelaar schrijft handmatig:
lock.acquire() # Kritieke sectie if iets_loopt_fout: return # vergeten release aan te roepen! lock.release()
Voordelen:
Nadelen:
Gebruik van with:
with lock: # Kritieke sectie if iets_loopt_fout: return
Voordelen:
Nadelen: