In Swift, per garantire la thread-safety, si utilizzano frequentemente GCD (Grand Central Dispatch) e code (DispatchQueue), NSLock, così come meccanismi moderni come actor (a partire da Swift 5.5).
Idea principale – l'accesso ai dati mutabili avviene o rigorosamente attraverso code sequenziali, o utilizzando sincronizzatori (lock).
Esempio con GCD:
class ThreadSafeArray<Element> { private var array: [Element] = [] private let queue = DispatchQueue(label: "com.example.arrayQueue", attributes: .concurrent) func append(_ item: Element) { queue.async(flags: .barrier) { self.array.append(item) } } func get(index: Int) -> Element? { var result: Element? queue.sync { result = self.array.indices.contains(index) ? self.array[index] : nil } return result } }
Approccio moderno: Actors (Swift 5.5+):
actor SafeCounter { private var value = 0 func increment() { value += 1 } func get() -> Int { return value } }
Entrambi gli approcci permettono di evitare le race condition e mantenere la coerenza dello stato.
Domanda:
Se si utilizza
DispatchQueue.syncall'interno della Main Queue, cosa succede e perché?
Risposta: Si verifica un deadlock, perché la Main Queue sta già eseguendo un'operazione e aspetta il completamento di un'operazione sincrona sulla stessa coda. Di conseguenza, la coda non può elaborare il compito successivo finché quello corrente non è completato, il che non accadrà mai.
Esempio:
DispatchQueue.main.sync { // Deadlock: questa linea non verrà mai eseguita }
Storia
Nel progetto è stata utilizzata una collezione mutabile, a cui si accedeva simultaneamente da più thread senza sincronizzazione. Questo ha portato a crash dell'app e bug difficili da rilevare a causa delle race condition: periodicamente si perdeva degli elementi, a volte si verificava un'uscita dai limiti dell'array.
Storia
Un sviluppatore ha utilizzato in modo errato DispatchQueue.sync sulla coda principale in tempo di esecuzione, causando un deadlock e un arresto completo dell'interfaccia utente per gli utenti dopo l'uscita dell'aggiornamento. Era necessario un fix urgente e un rollback della release.
Storia
Nel tentativo di rendere una classe thread-safe utilizzando NSLock, si è dimenticato di implementare lock/unlock per tutti i percorsi di uscita dal metodo (ad esempio, in caso di errore o di return all'interno di un guard), il che portava al potenziale di deadlock e gravi problemi di prestazioni dell'applicazione.