Ответ
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
}
}
// Компилятор гарантирует эксклюзивный доступ к свойствам актёра. Ответ 18+ 🔞
А, слушай, смотри, вот эта хуйня — deadlock, или, по-нашему, взаимная блокировка. Представь себе двух упырей, которые схватили друг друга за горло и оба ждут, пока первый разожмёт руку. И так до скончания века, пока система не накроется медным тазом. В iOS, на Swift, это как два потока устроили друг другу вечную пиздюлину за ресурсы. Чтобы такого не было, вот тебе стратегии, как не наступить на эти грабли.
1. Порядок — всё, блядь!
Если ты хватаешь несколько мьютексов (типа NSLock или os_unfair_lock), делай это всегда в одном и том же, блядь, порядке. По всей программе! Не выёбывайся.
let lockA = NSLock()
let lockB = NSLock()
// ПРАВИЛЬНО: Сначала всегда А, потом Б. Как в армии — старший по званию первый.
func safeAccess() {
lockA.lock()
defer { lockA.unlock() }
lockB.lock()
defer { lockB.unlock() }
// Вот тут твоя критическая секция, делай что хочешь
}
// НЕПРАВИЛЬНО: Если в другой функции сначала Б, а потом А — готовься к deadlock. Это как пытаться засунуть хуй в бутылку, начиная с горлышка и с донышка одновременно.
2. Не жди вечно, поставь таймер
Вместо того чтобы тупо висеть на lock(), используй попытку захвата с таймаутом. Если не получилось — отъебнись и прими решение.
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 (с версии 5.5) — это вообще охуенно, компилятор сам следит, чтобы к данным был эксклюзивный доступ.
// Пример с Actor (Swift 5.5+)
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}
// Компилятор сам, блядь, гарантирует, что к `balance` не будет гонок. Красота!
Вот и всё, ебать мои старые костыли. Следуй этим правилам, и твой код не повиснет в вечной борьбе двух упырей за ресурсы. А если повиснет — ну, значит, где-то накосячил, ищи, где порядок нарушил или sync не там вызвал. Удачи, ёпта!