← Назад к вопросам
Как исправить DeadLock?
2.0 Middle🔥 61 комментариев
#Многопоточность и асинхронность
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Понимание DeadLock
DeadLock (взаимная блокировка) возникает, когда два или более потока/процесса заблокированы, ожидая ресурсы, удерживаемые друг другом, создавая циклическую зависимость. В iOS это чаще всего происходит при работе с Grand Central Dispatch (GCD), NSLock, @synchronized или операциями NSOperation.
Ключевые условия DeadLock (Условия Коффмана)
- Взаимное исключение - ресурс не может быть использован одновременно.
- Удержание и ожидание - поток удерживает ресурс и ждет другой.
- Отсутствие вытеснения - ресурс нельзя принудительно отнять.
- Циклическое ожидание - образуется цикл потоков, ожидающих ресурсы.
Распространенные сценарии в iOS
1. Синхронный вызов на текущей очереди
// Классический пример
DispatchQueue.main.sync {
// Этот код никогда не выполнится, если вызван из main queue
print("This will deadlock")
}
2. Вложенные блокировки без порядка
let lockA = NSLock()
let lockB = NSLock()
// Поток 1
lockA.lock()
lockB.lock() // Может заблокироваться, если поток 2 взял lockB
// ...
lockB.unlock()
lockA.unlock()
// Поток 2
lockB.lock()
lockA.lock() // DeadLock!
// ...
lockA.unlock()
lockB.unlock()
Стратегии исправления и предотвращения
1. Уничтожение условия циклического ожидания
- Установите строгий порядок блокировок: всегда захватывайте ресурсы в одинаковом порядке.
// Правильно: одинаковый порядок во всех потоках
func accessResources() {
lockA.lock()
defer { lockA.unlock() }
lockB.lock()
defer { lockB.unlock() }
// Работа с ресурсами
}
2. Избегание синхронных вызовов на текущей очереди
- Используйте асинхронные dispatch или проверяйте текущую очередь:
func safeSync(on queue: DispatchQueue, work: () -> Void) {
if DispatchQueue.getSpecific(key: queueSpecificKey) == queueSpecificValue {
work() // Уже на нужной очереди
} else {
queue.sync(execute: work)
}
}
3. Использование timeout для блокировок
- Вместо
lock()используйтеtryLock(timeout:):
let lock = NSLock()
if lock.lock(before: Date().addingTimeInterval(1.0)) {
defer { lock.unlock() }
// Критическая секция
} else {
// Обработка таймаута, откат операции
}
4. Акторная модель (Swift 5.5+)
- Используйте акторы для безопасного доступа к состоянию:
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
return value
}
}
// Использование
Task {
let counter = Counter()
await counter.increment() // Безопасный доступ без deadlock
}
5. Проектирование без блокировок
- Используйте serial queues вместо примитивов синхронизации:
class ThreadSafeContainer {
private let queue = DispatchQueue(label: "com.example.serial")
private var data: [String] = []
func add(item: String) {
queue.async {
self.data.append(item)
}
}
}
6. Инструменты диагностики
- Включите Thread Sanitizer в Xcode
- Используйте осциллограммы времени выполнения в Instruments
- Добавляйте логирование порядка блокировок
Практический пример исправления
// Проблемный код
class BankAccount {
private let queue = DispatchQueue(label: "com.bank.account")
private var balance: Double = 1000
func transfer(to account: BankAccount, amount: Double) {
queue.sync {
// DeadLock: попытка захватить другую очередь
account.queue.sync {
self.balance -= amount
account.balance += amount
}
}
}
}
// Решение: централизованная блокировка
class BankAccountFixed {
private var balance: Double = 1000
func transfer(to account: BankAccountFixed, amount: Double,
using lock: NSLock) {
lock.lock()
defer { lock.unlock() }
guard self.balance >= amount else { return }
self.balance -= amount
account.balance += amount
}
}
Профилактические меры
- Принцип минимальной блокировки - блокируйте на минимально необходимое время
- Избегайте вызовов внешнего кода внутри критических секций
- Используйте высоуровневые абстракции -
DispatchQueue,OperationQueue - Пишите unit-тесты для многопоточного кода
- Проводите code review с фокусом на синхронизацию
DeadLock - сложная проблема, но системный подход к проектированию многопоточного кода, использование современных инструментов Swift и следование best practices позволяют полностью избегать её в production-коде. Наиболее перспективное направление - переход к async/await и акторной модели, которые значительно уменьшают пространство для ошибок синхронизации.