Какие основные способы избежать взаимной блокировки (deadlock) в iOS?

«Какие основные способы избежать взаимной блокировки (deadlock) в iOS?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Deadlock (взаимная блокировка) возникает, когда два или более потока бесконечно ожидают ресурсы, захваченные друг другом. Основные стратегии предотвращения в iOS/ Swift:

1. Единый порядок захвата блокировок Всегда захватывайте мьютексы (NSLock, os_unfair_lock) в строго одинаковом порядке во всей программе.

let lockA = NSLock()
let lockB = NSLock()

// ПРАВИЛЬНО: Всегда сначала A, потом B
func safeAccess() {
    lockA.lock()
    defer { lockA.unlock() }

    lockB.lock()
    defer { lockB.unlock() }

    // Критическая секция
}

// НЕПРАВИЛЬНО: Разный порядок в разных функциях ведёт к риску deadlock.

2. Использование блокировок с таймаутом Вместо бесконечного ожидания используйте попытку захвата с ограничением по времени.

if lockA.lock(before: Date().addingTimeInterval(0.5)) {
    defer { lockA.unlock() }
    // Успешный захват
} else {
    // Таймаут — принимаем решение (например, откат операции)
    print("Не удалось захватить блокировку, избегаем deadlock")
}

3. Предпочтение серийных очередей GCD мьютексам Серийная очередь (DispatchQueue) по своей природе исключает deadlock для задач в ней, если не вызывать sync из той же очереди.

let serialQueue = DispatchQueue(label: "com.example.serial")

serialQueue.async {
    // Эта критическая секция никогда не выполнится параллельно
    // с другим блоком на этой же очереди.
}

// ОПАСНО: Вызов sync из той же очереди вызовет deadlock.
serialQueue.sync { /* Этот код никогда не выполнится */ }

4. Избегание вложенной синхронизации По возможности проектируйте код так, чтобы не требовалось захватывать вторую блокировку, уже удерживая первую.

5. Использование высокоуровневых примитивов

  • DispatchGroup для координации групп задач.
  • NSOperationQueue с настройкой зависимостей (addDependency).
  • Актёры (Actors) в Swift (доступно с Swift 5.5) для изоляции состояния.
// Пример с Actor (Swift 5.5+)
actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount
    }
}
// Компилятор гарантирует эксклюзивный доступ к свойствам актёра.