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.
NSLockos_unfair_lockpthread_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.mainDispatchQueue.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.
DispatchQueuecon 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).