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:
-
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 } } -
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") } } -
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 } } -
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 } }
-
-
Opérations de contexte Core Data: Chaque
NSManagedObjectContexta 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.