> Какие средства синхронизации потоков существуют: 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 с barrierfinal 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] inself?.storage[key] = value}}}// Actor для изоляции состоянияactor Counter {private var count = 0func 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блокирует поток, а не просто ограничивает количество задач.
> Похожие задачи по mobile
На каких архитектурах писали и какие плюсы и минусы вы видите
Как работает дедлок и как его избежать
Какие механизмы синхронизации существуют для решения проблем многопоточности
Что такое кэш и как он работает
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью