← Назад к вопросам

Как исправить DeadLock?

2.0 Middle🔥 61 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Понимание DeadLock

DeadLock (взаимная блокировка) возникает, когда два или более потока/процесса заблокированы, ожидая ресурсы, удерживаемые друг другом, создавая циклическую зависимость. В iOS это чаще всего происходит при работе с Grand Central Dispatch (GCD), NSLock, @synchronized или операциями NSOperation.

Ключевые условия DeadLock (Условия Коффмана)

  1. Взаимное исключение - ресурс не может быть использован одновременно.
  2. Удержание и ожидание - поток удерживает ресурс и ждет другой.
  3. Отсутствие вытеснения - ресурс нельзя принудительно отнять.
  4. Циклическое ожидание - образуется цикл потоков, ожидающих ресурсы.

Распространенные сценарии в 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
    }
}

Профилактические меры

  1. Принцип минимальной блокировки - блокируйте на минимально необходимое время
  2. Избегайте вызовов внешнего кода внутри критических секций
  3. Используйте высоуровневые абстракции - DispatchQueue, OperationQueue
  4. Пишите unit-тесты для многопоточного кода
  5. Проводите code review с фокусом на синхронизацию

DeadLock - сложная проблема, но системный подход к проектированию многопоточного кода, использование современных инструментов Swift и следование best practices позволяют полностью избегать её в production-коде. Наиболее перспективное направление - переход к async/await и акторной модели, которые значительно уменьшают пространство для ошибок синхронизации.