> Какие механизмы синхронизации существуют для решения проблем многопоточности (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Цифровые решения

Стек: iOS, Swift

> Пример ответа

Короткий ответ

В iOS-разработке основные механизмы синхронизации: NSLock, NSRecursiveLock, NSCondition, NSConditionLock, NSDistributedLock, @synchronized, семафоры (DispatchSemaphore), группы (DispatchGroup), барьеры (DispatchWorkItemFlags.barrier), очереди с последовательным выполнением (DispatchQueue с qos), а также высокоуровневые абстракции: OperationQueue с maxConcurrentOperationCount, Actor (Swift 5.5+) и @MainActor. Выбор зависит от сценария: защита критической секции, координация задач или ограничение конкурентного доступа.

Подробное объяснение

Все механизмы решают три базовые проблемы: гонка данных (data race), взаимная блокировка (deadlock) и инверсия приоритетов. В iOS-контексте важно разделять низкоуровневые примитивы и высокоуровневые абстракции.

Низкоуровневые примитивы:

  • NSLock - нерекурсивная блокировка. Повторный захват тем же потоком вызывает deadlock.
  • NSRecursiveLock - позволяет повторный захват тем же потоком, но медленнее.
  • NSCondition - блокировка + условная переменная. Позволяет потоку ждать выполнения условия.
  • NSConditionLock - блокировка с целочисленным состоянием, удобна для пошаговых алгоритмов.
  • @synchronized - Objective-C конструкция, работает через ассоциированные объекты. Удобна, но имеет скрытые накладные расходы.

GCD-механизмы:

  • DispatchSemaphore - счётчик для ограничения количества одновременных доступов. wait() блокирует, signal() освобождает.
  • DispatchGroup - ожидание завершения группы задач. notify() для асинхронного продолжения.
  • DispatchWorkItemFlags.barrier - барьер в конкурентной очереди: задачи до барьера выполняются конкурентно, после - последовательно.
  • Последовательная очередь (DispatchQueue с attributes: []) - простейший способ сериализовать доступ к ресурсу.

Высокоуровневые абстракции:

  • OperationQueue с maxConcurrentOperationCount = 1 - аналог последовательной очереди с поддержкой зависимостей и отмены.
  • Actor - изолирует состояние внутри себя, компилятор гарантирует безопасность доступа. @MainActor - для UI-потока.
  • NSLock + NSCondition - исторически используются в legacy-коде.

Ключевые trade-off:

  • Блокировки проще, но подвержены deadlock и имеют накладные расходы на захват/освобождение.
  • GCD-механизмы более декларативны, но сложнее в отладке.
  • Actors - самый безопасный вариант, но требуют Swift 5.5+ и понимания async/await.

На практике

Для защиты свойства в классе чаще используют последовательную очередь или actor, а не NSLock, потому что это снижает риск ошибок. Для ограничения количества одновременных сетевых запросов - DispatchSemaphore. Для ожидания нескольких параллельных загрузок - DispatchGroup. Для синхронизации чтения/записи (много читателей, один писатель) - барьер в конкурентной очереди.

Пример из реального кода: кэш изображений. Чтение частое, запись редкая. Используем конкурентную очередь с барьером для записи и обычный доступ для чтения. Это даёт производительность без гонок.

Пример кода

SWIFT
final class ImageCache {
private var storage: [String: UIImage] = [:]
private let queue = DispatchQueue(label: "image.cache", attributes: .concurrent)
func image(forKey key: String) -> UIImage? {
queue.sync {
storage[key]
}
}
func setImage(_ image: UIImage, forKey key: String) {
queue.async(flags: .barrier) {
self.storage[key] = image
}
}
}

Пример с actor:

SWIFT
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
value
}
}

Как отвечать на собеседовании

Начни с классификации: примитивы блокировок, GCD-механизмы, высокоуровневые абстракции. Затем объясни, когда что выбирать. Обязательно упомяни проблемы: deadlock, priority inversion, race condition. Приведи пример из практики - кэш или пул ресурсов. Если спросят про производительность, сравни NSLock и os_unfair_lock (последний быстрее, но не рекурсивный). Для senior-уровня важно показать понимание, что синхронизация - это не только блокировки, но и проектирование: immutable-структуры, value types, избегание общего состояния.

Что проверяет интервьюер

  • Понимание различий между механизмами и их ограничений.
  • Умение выбирать инструмент под конкретный сценарий.
  • Знание типичных ошибок: deadlock при NSLock, использование @synchronized в hot path, неправильный DispatchSemaphore (забыли signal).
  • Понимание модели памяти Swift и гарантий actor.
  • Способность объяснить trade-off между производительностью и безопасностью.

Типичные ошибки

  • Использование NSLock без defer { lock.unlock() } - при исключении блокировка остаётся захваченной.
  • Путаница между DispatchSemaphore и DispatchGroup: семафор - для ограничения, группа - для ожидания завершения.
  • Применение @synchronized в Swift-коде - лучше использовать NSLock или actor.
  • Игнорирование os_unfair_lock как более быстрой альтернативы NSLock для простых случаев.
  • Создание deadlock при использовании нескольких блокировок в разном порядке.
  • Предположение, что DispatchQueue с attributes: .concurrent безопасен для записи без барьера.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью