Sobes.tech
Middle+

Come risolvere il problema della condizione di gara (race condition)?

sobes.tech AI

Risposta dell'AI

Utilizzare la sincronizzazione dei thread per accedere alle risorse condivise.

Approcci principali:

  • Mutex (Mutua esclusione): Blocca l'accesso a una risorsa per altri thread mentre uno la utilizza.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Acquisire il mutex
    // Sezione critica - accesso sicuro a sharedResource
    sharedResource += 1
    print("Valore della risorsa: \(sharedResource)")
    lock.unlock() // Rilasciare il mutex
}

// Esempio di chiamata da thread diversi
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Code seriali: Eseguono i task uno dopo l'altro, garantendo che solo un blocco di codice abbia accesso alla risorsa alla volta.
    • DispatchQueue.main
    • DispatchQueue.global() con attributo .serial
import Foundation

let serialQueue = DispatchQueue(label: "com.example.serialQueue")
var sharedResource = 0

func modifyResourceAsync() {
    serialQueue.async {
        // Il codice viene eseguito sequenzialmente in questa coda
        sharedResource += 1
        print("Valore della risorsa: \(sharedResource)")
    }
}

// Esempio di chiamate da thread/code diversi
// modifyResourceAsync()
// modifyResourceAsync()
  • Lock lettore-scrittore: Permettono a più thread di leggere contemporaneamente, ma solo a uno di scrivere.
    • DispatchQueue con barriere (.barrier) per la scrittura e accesso sincrono/asyncrono per la lettura.
import Foundation

let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
var sharedResource = 0

func readResource() {
    concurrentQueue.async { // Lettura - più lettori contemporaneamente
        print("Lettura risorsa: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Scrittura - un solo scrittore e nessuno legge/scrive
        sharedResource = newValue
        print("Scrittura risorsa: \(sharedResource)")
    }
}

// Esempio di chiamate
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Operazioni atomiche: Operazioni garantite per essere eseguite completamente senza interruzioni da altri thread. Sono usate per tipi di dati semplici (contatori, flag).
    • OSAtomicIncrement, OSAtomicDecrement (Obsoleti, ma il concetto è ancora valido)
    • <atomic> in C++11
#include <atomic>
#include <iostream>

std::atomic<int> atomicCounter(0);

void incrementCounter() {
    atomicCounter++; // Incremento atomico
    std::cout << "Contatore: " << atomicCounter << std::endl;
}

// Esempio in contesto iOS con C++ (uso con Objective-C++)
// Chiamare da thread diversi
  • Tipi di valore: Passare copie di strutture/enumerazioni invece di riferimenti a classi può aiutare a evitare problemi di race condition, se i dati non sono condivisi direttamente tra thread in modo modificabile.

La scelta del metodo dipende dalla natura del compito e dal livello di parallelismo richiesto. Per la maggior parte dei compiti in iOS, DispatchQueue (Seriale e Concorrente con barriere) è preferibile per la facilità d'uso e l'integrazione con Grand Central Dispatch (GCD).