Menedżer kontekstu dla operacji bezpiecznych dla wątków zapewnia, że blokada zasobu (np. pliku lub współdzielonych danych) zawsze jest poprawnie zwalniana, nawet w przypadku wystąpienia wyjątku. Jest to ważne dla programów wielowątkowych, w których wiele wątków może jednocześnie uzyskiwać dostęp do tych samych danych.
Historia pytania:
Potrzeba bezpieczeństwa wątków pojawiła się wraz z wprowadzeniem obliczeń wielowątkowych. W Pythonie od wersji 2.5 wprowadzono standardowy protokół menedżerów kontekstu, który ujednolica korzystanie z zasobów. Dla wątków oznacza to proste i niezawodne zarządzanie blokadami.
Problem:
Przy ręcznym zarządzaniu blokadami (acquire()/release()) łatwo zapomnieć o wywołaniu release, zwłaszcza przy przechwytywaniu wyjątku. Prowadzi to do zakleszczeń (deadlock). Menedżer kontekstu pomaga unikać takich błędów.
Rozwiązanie:
Użyj standardowych menedżerów kontekstu — albo za pomocą wbudowanego threading.Lock, albo implementując własny z użyciem metod magicznych __enter__ i __exit__.
Przykład kodu:
import threading lock = threading.Lock() # Standardowy sposób with lock: # Krytyczna sekcja print("Wykonywana operacja bezpieczna dla wątków") # Własny menedżer kontekstu 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("Indywidualny menedżer blokady działa!")
Kluczowe cechy:
Czy konieczne jest zaimplementowanie obu metod (enter, exit), aby klasa była menedżerem kontekstu?
Nie, wystarczy zaimplementować tylko __exit__, aby klasa działała w konstrukcji with, ale do korzystania z wewnętrznego stanu zazwyczaj potrzebny jest również __enter__. Jednak bez __enter__ nie można zwrócić obiektu/zasobu przez as, więc dla pełnej obsługi składni with oba metody są wymagane.
Czy należy jawnie wywołać release w finally, jeśli używa się with lock?
Nie, w konstrukcji with nie jest to konieczne: release jest wywoływane automatycznie przy wychodzeniu z bloku, nawet jeśli wystąpi wyjątek. To główna zaleta menedżerów kontekstu.
Czy można używać tej samej blokady z różnymi with jednocześnie w kilku wątkach?
Tak, to jest przewidziane w logice blokady: przy próbie uzyskania blokady zajętej przez inny wątek, bieżący wątek będzie zablokowany do momentu zwolnienia zasobu. Jednak niewłaściwa organizacja krytycznych sekcji może prowadzić do zakleszczeń, jeśli porządek przechwytywania jest inny w różnych miejscach kodu.
Programista ręcznie pisze:
lock.acquire() # Krytyczna sekcja if coś_poszło_nie_tak: return # zapomniano wywołać release! lock.release()
Zalety:
Wady:
Używając with:
with lock: # Krytyczna sekcja if coś_poszło_nie_tak: return
Zalety:
Wady: