Sobes.tech
Middle+

Comment résoudre le problème de condition de course (race condition) ?

sobes.tech IA

Réponse de l'IA

Utiliser la synchronisation des threads pour accéder aux ressources partagées.

Approches principales:

  • Mutex (Mutual Exclusion): Bloque l'accès à une ressource pour d'autres threads pendant qu'un thread l'utilise.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Acquérir le mutex
    // Section critique - accès sécurisé à sharedResource
    sharedResource += 1
    print("Valeur de la ressource: \(sharedResource)")
    lock.unlock() // Libérer le mutex
}

// Exemple d'appel depuis différents threads
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Queues sériales: Exécutent les tâches une par une, garantissant qu'un seul bloc de code a accès à la ressource à la fois.
    • DispatchQueue.main
    • DispatchQueue.global() avec l'attribut .serial
import Foundation

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

func modifyResourceAsync() {
    serialQueue.async {
        // Le code s'exécute séquentiellement dans cette queue
        sharedResource += 1
        print("Valeur de la ressource: \(sharedResource)")
    }
}

// Exemple d'appels depuis différents threads/queues
// modifyResourceAsync()
// modifyResourceAsync()
  • Verrous lecteur-écrivain: Permettent à plusieurs threads de lire la ressource simultanément, mais un seul peut écrire.
    • DispatchQueue avec barrières (.barrier) pour l'écriture et accès synchrone/asynchrone pour la lecture.
import Foundation

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

func readResource() {
    concurrentQueue.async { // Lecture - plusieurs lecteurs en même temps
        print("Lecture de la ressource: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Écriture - un seul écrivain et personne ne lit/écrit
        sharedResource = newValue
        print("Écriture de la ressource: \(sharedResource)")
    }
}

// Exemple d'appels
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Opérations atomiques: Opérations garantissant une exécution complète sans interruption par d'autres threads. Utilisées pour des types de données simples (compteurs, drapeaux).
    • OSAtomicIncrement, OSAtomicDecrement (Obsolètes, mais la conception reste valable)
    • <atomic> en C++11
#include <atomic>
#include <iostream>

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

void incrementCounter() {
    atomicCounter++; // Incrément atomique
    std::cout << "Compteur: " << atomicCounter << std::endl;
}

// Exemple en contexte iOS avec C++ (utilisation avec Objective-C++)
// Appeler depuis différents threads
  • Types de valeur: Transférer des copies de structures/enums au lieu de références à des classes peut aider à éviter les problèmes de conditions de course si les données ne sont pas partagées directement entre threads de manière modifiable.

Le choix de la méthode dépend de la nature de la tâche et du niveau de parallélisme requis. Pour la plupart des tâches iOS, DispatchQueue (sériale et concurrente avec barrières) est préféré pour sa facilité d'utilisation et son intégration avec Grand Central Dispatch (GCD).