Sobes.tech
Middle

Cos'è una condizione di gara (race condition)?

sobes.tech AI

Risposta dell'AI

La condizione di race si verifica quando due o più thread o processi accedono contemporaneamente a una risorsa condivisa (ad esempio, una variabile, un file, un database) e il risultato dell'operazione dipende dall'ordine in cui questi thread/processi eseguono le loro azioni. A causa dell'ordine imprevedibile di accesso e modifica dei dati, il risultato può essere errato o inaspettato.

Esempio in iOS: Supponiamo di avere un contatore count, a cui accedono contemporaneamente due thread per incrementarlo.

var count = 0

func incrementCount() {
    // Il Thread 1 legge count = 5
    // Il Thread 2 legge count = 5
    let currentCount = count
    // Il Thread 1 calcola newCount = 6
    // Il Thread 2 calcola newCount = 6
    let newCount = currentCount + 1
    // Il Thread 1 scrive count = 6
    // Il Thread 2 scrive count = 6 (sovrascrivendo il risultato del Thread 1)
    count = newCount
}

// Se due thread chiamano incrementCount() contemporaneamente,
// il risultato atteso sarebbe count = 2,
// ma a causa della condizione di race, count può diventare 1.

Per prevenire le condizioni di race, si utilizzano meccanismi di sincronizzazione:

  • Lock/Muoro: Blocca l'accesso alla risorsa mentre il thread la utilizza.
    import Foundation
    
    let lock = NSLock()
    var count = 0
    
    func incrementCount() {
        lock.lock() // Acquisisce il lock
        let currentCount = count
        let newCount = currentCount + 1
        count = newCount
        lock.unlock() // Rilascia il lock
    }
    
  • Code (Queue): Uso di code GCD sequenziali.
    import Foundation
    
    let serialQueue = DispatchQueue(label: "com.myapp.counterQueue")
    var count = 0
    
    func incrementCount() {
        serialQueue.async {
            let currentCount = count
            let newCount = currentCount + 1
            count = newCount
        }
    }
    
  • Operazioni atomiche: Operazioni indivisibili a livello di istruzioni del processore. In Swift, si può usare OSAtomic... (obsoleto) o Atomic da @_implementationOnly import Atomics (sperimentale), o approcci basati su lock/queue.

La scelta del metodo di sincronizzazione dipende dallo scenario specifico e dai requisiti di prestazioni. È importante capire che la sincronizzazione può introdurre carico aggiuntivo e potenzialmente portare a deadlock se non implementata correttamente.