Что такое deadlock и race condition? Как их избежать?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Deadlock и Race Condition: основные проблемы многопоточности
Что такое Deadlock (Взаимная блокировка)?
Deadlock — это ситуация, когда два или более потока находятся в состоянии бесконечного ожидания ресурсов, захваченных друг другом, образуя циклическую зависимость. Классическое условие deadlock возникает при одновременном выполнении четырёх условий (условия Коффмана):
- Взаимное исключение — ресурс может использоваться только одним потоком.
- Удержание и ожидание — поток удерживает один ресурс и ожидает другой.
- Отсутствие вытеснения — ресурс нельзя отнять у потока.
- Циклическое ожидание — потоки образуют кольцевую цепочку ожидания.
Пример 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:
-
Упорядоченная блокировка (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() } -
Использование одного блокировщика — вместо нескольких locks использовать один.
-
Атомарные операции и lock-free структуры:
let atomicCounter = Atomic<Int>(0) DispatchQueue.concurrentPerform(iterations: 1000) { _ in atomicCounter.mutate { $0 += 1 } } -
Использование
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:
-
Очереди GCD с правильными QoS:
let serialQueue = DispatchQueue(label: "com.example.serial") var safeCounter = 0 serialQueue.async { safeCounter += 1 // Гарантированно безопасно } -
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 // Запись эксклюзивно } } -
Акторы (Actor) в Swift Concurrency (начиная с Swift 5.5):
actor CounterActor { private var value = 0 func increment() { value += 1 // Компилятор гарантирует thread-safety } func getValue() -> Int { value } } -
Имутабельность и value-типы:
struct ImmutableData { let items: [String] // let делает потокобезопасным }
Проактивные подходы:
- Статический анализ кода (Thread Sanitizer в Xcode)
- Юнит-тесты для многопоточности
- Принцип минимальных привилегий — ограничение области видимости разделяемых данных
- Мониторинг и логирование блокировок в production
Наиболее современный подход в iOS-разработке — использование Swift Concurrency с async/await и акторами, которые значительно снижают вероятность обеих проблем за счёт абстракций, управляемых компилятором. Однако понимание низкоуровневых механизмов остаётся критически важным для работы с legacy-кодом и оптимизации производительности.