Sobes.tech
Middle+

Jak synchronizować dane między wątkami i zapewnić spójność kontekstu?

sobes.tech AI

Odpowiedź od AI

Aby zsynchronizować dane i zapewnić spójność kontekstu w wielowątkowej aplikacji na iOS, stosuje się następujące podejścia:

  1. NSLock / Lock: Proste mutexy do blokowania krytycznych sekcji kodu.

    import Foundation
    
    class ThreadSafeCounter {
        private var count: Int = 0
        private let lock = NSLock()
    
        func increment() {
            lock.lock()
            defer { lock.unlock() } // Gwarantowane zwolnienie blokady
            count += 1
        }
    
        func getCount() -> Int {
            lock.lock()
            defer { lock.unlock() }
            return count
        }
    }
    
  2. NSRecursiveLock: Pozwala na ponowne nabycie blokady z tego samego wątku.

    import Foundation
    
    class RecursiveLocker {
        private let lock = NSRecursiveLock()
    
        func firstMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Pierwsza metoda")
            secondMethod() // Ponowne nabycie jest możliwe
        }
    
        func secondMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Druga metoda")
        }
    }
    
  3. NSCondition / NSConditionLock: Pozwalają wątkom oczekiwać na określony warunek lub wartość przed kontynuacją. Używa się ich do implementacji wzorca producent-konsument.

    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() // Czeka, aż pojawi się wolne miejsce
            }
    
            items.append(item)
            print("Dodano: \(item), Aktualny bufor: \(items)")
            condition.signal() // Sygnalizuje oczekującym wątkom
        }
    
        func removeItem() -> Int {
            condition.lock()
            defer { condition.unlock() }
    
            while items.isEmpty {
                condition.wait() // Czeka, aż pojawi się element
            }
    
            let item = items.removeFirst()
            print("Usunięto: \(item), Aktualny bufor: \(items)")
            condition.signal() // Sygnalizuje oczekującym wątkom
            return item
        }
    }
    
  4. Grand Central Dispatch (GCD):

    • Kolejki seryjne: Wykonują zadania sekwencyjnie, jedno po drugim na tym samym wątku, zapewniając atomowość dostępu do zasobów.

    • Kolejki współbieżne z flagami Barrier: Pozwalają na wykonywanie odczytów równocześnie, ale operacje zapisu (write) są wykonywane wyłącznie. Implementuje to model locka odczyt-zapis.

      import Foundation
      
      class ThreadSafeArray<Element> {
          private var array: [Element] = []
          private let queue =
          DispatchQueue(label: "com.mycompany.threadsafearray", attributes: .concurrent) // Kolejka współbieżna
      
          func append(_ newElement: Element) {
              queue.async(flags: .barrier) { // Barrier dla zapisu
                  self.array.append(newElement)
              }
          }
      
          func get(at index: Int) -> Element? {
              var result: Element?
              queue.sync { // Synchronizacja dla odczytu (lub async, jeśli kolejność nie ma znaczenia)
                  if index < self.array.count {
                      result = self.array[index]
                  }
              }
              return result
          }
      }
      
  5. Operacje kontekstu Core Data: Każdy NSManagedObjectContext ma własny prywatny, serializowany wątek. Dostęp do obiektów i kontekstu powinien być wykonywany tylko z tego wątku.

    import CoreData
    
    // Przykład zapisywania w kontekście Core Data
    let context = // Uzyskany kontekst
    context.perform { // Wykonanie na prywatnym wątku kontekstu
        // Praca z obiektami Core Data
        do {
            try context.save()
        } catch {
            // Obsługa błędów
        }
    }
    

Wybór metody zależy od konkretnego zadania i wymagań dotyczących wydajności. GCD z kolejkami seryjnymi lub współbieżnymi z barierami jest często najbardziej preferowaną metodą ze względu na jej efektywność i łatwość użycia. Dla Core Data, zawsze używaj metod perform lub performAndWait.