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

Что такое deadlock и race condition? Как их избежать?

2.8 Senior🔥 112 комментариев
#CI/CD и инструменты разработки#Soft Skills и карьера#SwiftUI

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

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

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

Deadlock и Race Condition: основные проблемы многопоточности

Что такое Deadlock (Взаимная блокировка)?

Deadlock — это ситуация, когда два или более потока находятся в состоянии бесконечного ожидания ресурсов, захваченных друг другом, образуя циклическую зависимость. Классическое условие deadlock возникает при одновременном выполнении четырёх условий (условия Коффмана):

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

Пример deadlock в Swift:

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

DispatchQueue.global().async {
    lockA.lock()
    Thread.sleep(forTimeInterval: 0.1)
    lockB.lock() // Ожидание lockB, который удерживается другим потоком
    lockB.unlock()
    lockA.unlock()
}

DispatchQueue.global().async {
    lockB.lock()
    Thread.sleep(forTimeInterval: 0.1)
    lockA.lock() // Ожидание lockA, который удерживается первым потоком
    lockA.unlock()
    lockB.unlock()
}

Что такое Race Condition (Состояние гонки)?

Race Condition — это ошибка, когда результат выполнения программы зависит от непредсказуемого порядка выполнения потоков. Возникает при одновременном доступе к общим данным без синхронизации.

Пример race condition:

var counter = 0

DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    counter += 1 // Неатомарная операция!
}
// Результат counter будет непредсказуемым (< 1000)

Стратегии предотвращения

Для предотвращения deadlock:

  1. Упорядоченная блокировка (Lock ordering):

    // Всегда захватывать locks в определённом порядке
    func orderedLocking(lock1: NSLock, lock2: NSLock) {
        let (first, second) = lock1.hashValue < lock2.hashValue 
            ? (lock1, lock2) : (lock2, lock1)
        first.lock()
        second.lock()
        // Критическая секция
        second.unlock()
        first.unlock()
    }
    
  2. Использование одного блокировщика — вместо нескольких locks использовать один.

  3. Атомарные операции и lock-free структуры:

    let atomicCounter = Atomic<Int>(0)
    DispatchQueue.concurrentPerform(iterations: 1000) { _ in
        atomicCounter.mutate { $0 += 1 }
    }
    
  4. Использование NSLock с таймаутом:

    if lockA.lock(before: Date().addingTimeInterval(0.1)) {
        if lockB.lock(before: Date().addingTimeInterval(0.1)) {
            // Успех
            lockB.unlock()
            lockA.unlock()
        } else {
            lockA.unlock() // Откат при неудаче
        }
    }
    

Для предотвращения race condition:

  1. Очереди GCD с правильными QoS:

    let serialQueue = DispatchQueue(label: "com.example.serial")
    var safeCounter = 0
    
    serialQueue.async {
        safeCounter += 1 // Гарантированно безопасно
    }
    
  2. Reader-writer блокировки для оптимизации чтения:

    let concurrentQueue = DispatchQueue(label: "com.example.concurrent", 
                                        attributes: .concurrent)
    var data: [String] = []
    
    func readData() -> [String] {
        concurrentQueue.sync { data } // Множественное чтение параллельно
    }
    
    func writeData(_ newData: [String]) {
        concurrentQueue.async(flags: .barrier) {
            data = newData // Запись эксклюзивно
        }
    }
    
  3. Акторы (Actor) в Swift Concurrency (начиная с Swift 5.5):

    actor CounterActor {
        private var value = 0
        
        func increment() {
            value += 1 // Компилятор гарантирует thread-safety
        }
        
        func getValue() -> Int {
            value
        }
    }
    
  4. Имутабельность и value-типы:

    struct ImmutableData {
        let items: [String] // let делает потокобезопасным
    }
    

Проактивные подходы:

  • Статический анализ кода (Thread Sanitizer в Xcode)
  • Юнит-тесты для многопоточности
  • Принцип минимальных привилегий — ограничение области видимости разделяемых данных
  • Мониторинг и логирование блокировок в production

Наиболее современный подход в iOS-разработке — использование Swift Concurrency с async/await и акторами, которые значительно снижают вероятность обеих проблем за счёт абстракций, управляемых компилятором. Однако понимание низкоуровневых механизмов остаётся критически важным для работы с legacy-кодом и оптимизации производительности.