Назад к вопросам
Middle+
85
questionbank

Как синхронизировать данные между потоками и обеспечить согласованность контекста?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Для синхронизации данных и обеспечения согласованности контекста в многопоточном приложении на iOS используются следующие подходы:

  1. NSLock / Lock: Простые мьютексы для блокировки критических секций кода.

    import Foundation
    
    class ThreadSafeCounter {
        private var count: Int = 0
        private let lock = NSLock()
    
        func increment() {
            lock.lock()
            defer { lock.unlock() } // Гарантированное снятие блокировки
            count += 1
        }
    
        func getCount() -> Int {
            lock.lock()
            defer { lock.unlock() }
            return count
        }
    }
    
  2. NSRecursiveLock: Позволяет повторно захватывать блокировку из того же потока.

    import Foundation
    
    class RecursiveLocker {
        private let lock = NSRecursiveLock()
    
        func firstMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("First method")
            secondMethod() // Повторный захват возможен
        }
    
        func secondMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Second method") במדויק
        }
    }
    
  3. NSCondition / NSConditionLock: Позволяет потокам ждать определенного условия (Condition) или значения (ConditionLock) перед продолжением выполнения. Используется для реализации producer-consumer паттерна.

    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() // Ждем, пока появится свободное место
            }
    
            items.append(item)
            print("Added: \(item), Current Buffer: \(items)")
            condition.signal() // Сигнализируем потокам, которые ждут élément
        }
    
        func removeItem() -> Int {
            condition.lock()
            defer { condition.unlock() }
    
            while items.isEmpty {
                condition.wait() // Ждем, пока появится элемент
            }
    
            let item = items.removeFirst()
            print("Removed: \(item), Current Buffer: \(items)")
            condition.signal() // Сигнализируем потокам, которые ждут место
            return item
        }
    }
    
  4. Grand Central Dispatch (GCD):

    • Serial Queues: Выполняют задачи последовательно, по одной за раз на данном потоке, что обеспечивает атомарность доступа к ресурсам.

    • Concurrent Queues с Barrier Flags: Позволяют выполнять чтение concurrently, но операции записи (write) выполняются эксклюзивно. Это реализует модель read-write lock.

      import Foundation
      
      class ThreadSafeArray<Element> {
          private var array: [Element] = []
          private let queue =
          DispatchQueue(label: "com.mycompany.threadsafearray", attributes: .concurrent) // Concurrent queue
      
          func append(_ newElement: Element) {
              queue.async(flags: .barrier) { // Barrier для записи
                  self.array.append(newElement)
              }
          }
      
          func get(at index: Int) -> Element? {
              var result: Element?
              queue.sync { // Sync для чтения (или async, если порядок read не важен)
                  if index < self.array.count {
                      result = self.array[index]
                  }
              }
              return result
          }
      }
      
  5. Операции Core Data Context: Каждый NSManagedObjectContext имеет свой собственный частный сериалный поток. Доступ к объектам и контексту должен производиться только из этого потока.

    import CoreData
    
    // Пример сохранения в контекст Core Data
    let context = // Полученный контекст
    context.perform { // Выполнение на приватном потоке контекста
        // Работа с объектами Core Data здесь
        do {
            try context.save()
        } catch {
            // Обработка ошибки
        }
    }
    

Выбор метода зависит от конкретной задачи и требований к производительности. GCD с серийными очередями или concurrent очередями с барьерами часто является наиболее предпочтительным подходом из-за своей эффективности и удобства использования. Для Core Data обязательно используйте методы perform или performAndWait.