Ответ
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
}
}
// Компилятор гарантирует эксклюзивный доступ к свойствам актёра.