Un gestore di contesto per operazioni thread-safe garantisce che il blocco di una risorsa (come un file o dati condivisi) venga sempre rilasciato correttamente, anche in caso di eccezione. Questo è importante per i programmi multithread, in cui più thread possono accedere contemporaneamente agli stessi dati.
Storia della questione:
La necessità di garantire la thread-safety è emersa con l'introduzione dei calcoli multithread. In Python, a partire dalla versione 2.5, è stato introdotto il protocollo standard dei gestori di contesto, che unifica la gestione delle risorse. Per i thread, ciò significa una gestione semplice e affidabile dei lock.
Problema:
Con la gestione manuale dei lock (acquire()/release()), è facile dimenticare di chiamare release, soprattutto quando si intercetta un'eccezione. Questo porta a deadlock. Un gestore di contesto aiuta a evitare tali errori.
Soluzione:
Utilizza i gestori di contesto standard — sia utilizzando il threading.Lock integrato, sia implementando il tuo proprio con i metodi magici __enter__ e __exit__.
Esempio di codice:
import threading lock = threading.Lock() # Metodo standard with lock: # Sezione critica print("Esecuzione di un'operazione thread-safe") # Gestore di contesto personalizzato 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("Il gestore di lock personalizzato funziona!")
Caratteristiche chiave:
È obbligatorio implementare entrambi i metodi (enter, exit) affinché una classe sia un gestore di contesto?
No, è sufficiente implementare solo __exit__ affinché la classe funzioni nella struttura with, ma per utilizzare uno stato interno di solito è necessario anche __enter__. Tuttavia, senza __enter__, non è possibile restituire un oggetto/risorsa tramite as, quindi per un supporto completo della sintassi with sono necessari entrambi i metodi.
È necessario chiamare esplicitamente release in finally se si usa with lock?
No, con la struttura with non è necessario: release viene chiamato automaticamente all'uscita dal blocco, anche se si è verificata un'eccezione. Questo è il principale vantaggio dei gestori di contesto.
È possibile utilizzare lo stesso lock con diversi with contemporaneamente in più thread?
Sì, questo è previsto dalla logica del lock: quando si tenta di acquisire un lock già preso da un altro thread, il thread corrente verrà bloccato fino al rilascio della risorsa. Tuttavia, un'errata organizzazione delle sezioni critiche può portare a deadlock se l'ordine di acquisizione è diverso in diverse parti del codice.
Un sviluppatore scrive manualmente:
lock.acquire() # Sezione critica if qualcosa_non_va: return # dimenticato di chiamare release! lock.release()
Vantaggi:
Svantaggi:
Si utilizza with:
with lock: # Sezione critica if qualcosa_non_va: return
Vantaggi:
Svantaggi: