> Какие средства синхронизации потоков существуют: mutex, semaphore, NSLock (iOS, Swift)

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

Компании: VK

Стек: iOS, Swift

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

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

Основные средства синхронизации в iOS: NSLock, NSRecursiveLock, NSConditionLock, NSCondition, DispatchSemaphore, DispatchGroup, OperationQueue, а также высокоуровневые DispatchQueue с барьерами и Actor в Swift. Mutex - это низкоуровневый примитив из pthread_mutex, который лежит в основе NSLock. Выбор зависит от задачи: для защиты критической секции - lock или actor, для ограничения количества одновременных операций - semaphore, для координации зависимых задач - group.

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

В iOS-разработке синхронизация нужна для защиты разделяемых ресурсов от гонок данных. Основные примитивы:

  • Mutex (pthread_mutex_t) - самый низкоуровневый, блокирует поток, если ресурс занят. NSLock - тонкая обёртка над ним. Нельзя захватывать повторно из того же потока - будет deadlock.
  • NSRecursiveLock - позволяет повторный захват из того же потока, ведёт счётчик. Полезен для рекурсивных функций, но медленнее обычного lock.
  • NSConditionLock - блокировка с условием, позволяет разблокировать только при выполнении определённого состояния.
  • NSCondition - комбинация mutex и condition variable, позволяет потоку ждать сигнала от другого потока.
  • DispatchSemaphore - счётчик, ограничивает количество одновременных доступов. wait() уменьшает, signal() увеличивает. Классика для пула ресурсов.
  • DispatchGroup - для ожидания завершения группы задач, не блокирует ресурс, а ждёт выполнения.
  • DispatchQueue с барьерами (barrier) - для reader-writer паттерна: чтение параллельно, запись исключительно.
  • Actor (Swift 5.5+) - высокоуровневый механизм, гарантирует изоляцию состояния, компилятор сам вставляет синхронизацию. Рекомендуется для нового кода.

Важно понимать trade-off: низкоуровневые примитивы дают больше контроля, но требуют ручного управления и подвержены ошибкам. Высокоуровневые - безопаснее, но менее гибкие.

На практике

В реальных iOS-проектах чаще всего используются:

  • DispatchQueue для сериализации доступа к UI или модели.
  • Actor для изоляции состояния в Swift-коде.
  • DispatchSemaphore для ограничения параллельных сетевых запросов.
  • NSLock - в легаси-коде или когда нужен минимальный оверхед.

Например, для кэша изображений типичен reader-writer паттерн с DispatchQueue и barrier. Для потокобезопасного счётчика - OSAllocatedUnfairLock (iOS 16+) или NSLock.

Пример кода

SWIFT
// Reader-writer с barrier
final class ThreadSafeCache<Key: Hashable, Value> {
private var storage: [Key: Value] = [:]
private let queue = DispatchQueue(label: "cache.queue", attributes: .concurrent)
func value(for key: Key) -> Value? {
queue.sync { storage[key] }
}
func set(_ value: Value, for key: Key) {
queue.async(flags: .barrier) { [weak self] in
self?.storage[key] = value
}
}
}
// Actor для изоляции состояния
actor Counter {
private var count = 0
func increment() {
count += 1
}
func getCount() -> Int {
count
}
}
// Semaphore для ограничения параллельных запросов
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
DispatchQueue.global().async {
semaphore.wait()
defer { semaphore.signal() }
// сетевой запрос
}
}

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

Начните с классификации: низкоуровневые (mutex, lock) и высокоуровневые (actor, queue). Для каждого назовите сценарий использования. Обязательно упомяните deadlock и race condition - это покажет понимание проблем. Приведите пример из практики: как защищали кэш или делали потокобезопасный синглтон. Если спросят про производительность - отметьте, что actor имеет оверхед, а OSAllocatedUnfairLock быстрее NSLock. Не углубляйтесь в детали реализации, если не просят.

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

  • Понимание различий между mutex, semaphore и lock.
  • Умение выбирать инструмент под задачу.
  • Знание проблем: deadlock, priority inversion, race condition.
  • Практический опыт с GCD и Swift Concurrency.
  • Понимание trade-off между производительностью и безопасностью.

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

  • Путаница между semaphore и mutex: semaphore не привязан к потоку, mutex - привязан.
  • Использование NSLock в рекурсивной функции без NSRecursiveLock - deadlock.
  • Захват lock внутри DispatchQueue.sync - риск deadlock при вложенных вызовах.
  • Игнорирование defer при semaphore.wait() - утечка счётчика при ошибке.
  • Переусложнение: использование низкоуровневых примитивов там, где достаточно actor или серийной очереди.
  • Непонимание, что DispatchSemaphore блокирует поток, а не просто ограничивает количество задач.

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

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