Sobes.tech
Middle+

Comment synchroniser les données entre les threads et assurer la cohérence du contexte?

sobes.tech IA

Réponse de l'IA

Pour la synchronisation des données et assurer la cohérence du contexte dans une application multithread sur iOS, les approches suivantes sont utilisées:

  1. NSLock / Lock: Mutex simples pour bloquer les sections critiques du code.

    import Foundation
    
    class ThreadSafeCounter {
        private var count: Int = 0
        private let lock = NSLock()
    
        func increment() {
            lock.lock()
            defer { lock.unlock() } // Libération garantie du verrou
            count += 1
        }
    
        func getCount() -> Int {
            lock.lock()
            defer { lock.unlock() }
            return count
        }
    }
    
  2. NSRecursiveLock: Permet de réacquérir le verrou depuis le même thread.

    import Foundation
    
    class RecursiveLocker {
        private let lock = NSRecursiveLock()
    
        func firstMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Première méthode")
            secondMethod() // La réacquisition est possible
        }
    
        func secondMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Deuxième méthode")
        }
    }
    
  3. NSCondition / NSConditionLock: Permettent aux threads d'attendre une condition spécifique ou une valeur avant de continuer. Utilisé pour implémenter le pattern producteur-consommateur.

    import Foundation
    
    class Buffer {
        private var items: [Int] = []
        private let condition = NSCondition()
        private let capacity = 5
    
        func addItem(_ item: Int) {
            condition.lock()
            defer { condition.unlock() }
    
            while items.count == capacity {
                condition.wait() // Attendre jusqu'à ce qu'il y ait de l'espace libre
            }
    
            items.append(item)
            print("Ajouté: \(item), Buffer actuel: \(items)")
            condition.signal() // Signaler aux threads qui attendent
        }
    
        func removeItem() -> Int {
            condition.lock()
            defer { condition.unlock() }
    
            while items.isEmpty {
                condition.wait() // Attendre jusqu'à ce qu'il y ait un élément
            }
    
            let item = items.removeFirst()
            print("Supprimé: \(item), Buffer actuel: \(items)")
            condition.signal() // Signaler aux threads qui attendent de l'espace
            return item
        }
    }
    
  4. Grand Central Dispatch (GCD):

    • Queues sériales: Exécutent les tâches de manière séquentielle, une à la fois sur le même thread, garantissant l'atomicité de l'accès aux ressources.

    • Queues concurrentes avec barrières: Permettent de faire des lectures en parallèle, mais les opérations d'écriture (write) s'exécutent de manière exclusive. Cela implémente un modèle de verrouillage lecture-écriture.

      import Foundation
      
      class ThreadSafeArray<Element> {
          private var array: [Element] = []
          private let queue =
          DispatchQueue(label: "com.mycompany.threadsafearray", attributes: .concurrent) // Queue concurrente
      
          func append(_ newElement: Element) {
              queue.async(flags: .barrier) { // Barrière pour l'écriture
                  self.array.append(newElement)
              }
          }
      
          func get(at index: Int) -> Element? {
              var result: Element?
              queue.sync { // Synchronisation pour la lecture (ou async, si l'ordre n'est pas important)
                  if index < self.array.count {
                      result = self.array[index]
                  }
              }
              return result
          }
      }
      
  5. Opérations de contexte Core Data: Chaque NSManagedObjectContext a son propre thread sérialisé privé. L'accès aux objets et au contexte doit se faire uniquement depuis ce thread.

    import CoreData
    
    // Exemple de sauvegarde dans le contexte Core Data
    let context = // Contexte obtenu
    context.perform { // Exécution sur le thread privé du contexte
        // Travailler avec les objets Core Data ici
        do {
            try context.save()
        } catch {
            // Gestion de l'erreur
        }
    }
    

Le choix de la méthode dépend de la tâche spécifique et des exigences de performance. GCD avec des queues sériales ou des queues concurrentes avec barrières est souvent la méthode préférée en raison de son efficacité et de sa facilité d'utilisation. Pour Core Data, utilisez toujours les méthodes perform ou performAndWait.