> Может ли дедлок возникать между задачами на разных очередях (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Bip.ru

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Да, дедлок может возникать между задачами на разных очередях. Классический пример - две очереди, каждая из которых синхронно ожидает завершения задачи, поставленной в другую очередь. Также дедлок возможен при использовании семафоров, групп или DispatchWorkItem с wait() через разные очереди. Важно понимать: дедлок - это свойство логики синхронизации, а не конкретной очереди.

Подробное объяснение

Дедлок - это состояние, когда две или более задач блокируют друг друга, ожидая ресурсы, которые удерживают друг друга. Очереди GCD - это просто механизм исполнения, но если задачи на разных очередях синхронно ожидают завершения друг друга, возникает взаимная блокировка.

Типичный сценарий:

  • очередь A выполняет задачу, которая вызывает sync на очередь B
  • очередь B выполняет задачу, которая вызывает sync на очередь A

Обе задачи блокируются навсегда, потому что ни одна не может завершиться, пока другая не освободит очередь.

Важно различать:

  • Serial queue - задачи выполняются последовательно, поэтому sync на ту же самую очередь гарантированно даст дедлок
  • Concurrent queue - sync на ту же очередь не даст дедлок, но sync между двумя разными очередями может дать дедлок, если логика взаимного ожидания

Также дедлок может возникнуть при использовании:

  • DispatchSemaphore - если задача на очереди A ждет семафор, который освобождает задача на очереди B, а та, в свою очередь, ждет завершения задачи на очереди A
  • DispatchGroup - если wait() вызывается внутри задачи, которая должна завершиться для notify
  • NSLock / os_unfair_lock - если блокировки захватываются в разном порядке на разных очередях

На практике

В iOS-разработке дедлоки между очередями чаще всего возникают при:

  • синхронном обращении к UIKit из фоновой очереди через DispatchQueue.main.sync - если main queue уже заблокирована ожиданием фоновой задачи
  • использовании semaphore.wait() внутри задачи, которая выполняется на той же очереди, что и задача, которая должна освободить семафор
  • работе с Core Data или Realm, когда контексты/хранилища блокируются на разных очередях

Практические рекомендации:

  • избегать sync между очередями, особенно в production-коде
  • использовать async с completion или async/await вместо синхронного ожидания
  • если нужен sync, убедиться, что нет циклических зависимостей
  • для семафоров - всегда проверять таймауты, чтобы не блокировать навсегда
  • использовать DispatchQueue.main.async вместо sync, если задача не требует немедленного результата

Пример кода

SWIFT
// Классический дедлок между двумя очередями
let queueA = DispatchQueue(label: "queueA")
let queueB = DispatchQueue(label: "queueB")
queueA.async {
print("A: start")
queueB.sync {
print("B: inside A")
}
print("A: end")
}
queueB.async {
print("B: start")
queueA.sync {
print("A: inside B")
}
print("B: end")
}
// Результат: обе задачи заблокированы навсегда
SWIFT
// Дедлок с семафором на разных очередях
let semaphore = DispatchSemaphore(value: 0)
queueA.async {
semaphore.wait() // ждет вечно
}
queueB.async {
// эта задача должна освободить семафор,
// но она ждет завершения задачи на queueA
queueA.sync {
semaphore.signal()
}
}

Как отвечать на собеседовании

Начни с прямого ответа - да, может. Затем приведи конкретный пример с двумя очередями и sync. Объясни механизм: каждая задача блокирует очередь, на которой выполняется, и ждет завершения задачи на другой очереди. Подчеркни, что дедлок - это свойство логики синхронизации, а не очереди как таковой.

Затем расширь ответ: упомяни семафоры, группы, блокировки. Покажи, что понимаешь разницу между serial и concurrent очередями. Если спросят про async/await - скажи, что это снижает риск дедлоков, но не исключает их полностью (например, при использовании withCheckedContinuation с неправильной логикой).

Хорошо добавить практический пример из iOS: DispatchQueue.main.sync из фоновой очереди, когда main queue уже заблокирована. Это покажет, что ты сталкивался с реальными проблемами.

Что проверяет интервьюер

Интервьюер проверяет:

  • понимание модели исполнения GCD: serial vs concurrent, sync vs async
  • умение анализировать конкурентный код и находить взаимные блокировки
  • знание примитивов синхронизации: семафоры, группы, блокировки
  • способность объяснить, почему дедлок возникает именно в данном сценарии
  • практический опыт: сталкивался ли кандидат с дедлоками в реальных проектах и как их решал

Для senior-позиции важно не просто назвать определение, а показать системное мышление: объяснить, как избежать дедлоков на уровне архитектуры, а не только в конкретном фрагменте кода.

Типичные ошибки

  • утверждение, что дедлок возможен только на одной очереди - это неверно, между очередями он тоже возникает
  • смешение sync на ту же очередь и sync на другую очередь - это разные сценарии
  • игнорирование роли семафоров и групп - дедлоки возникают не только из-за sync
  • уверенность, что concurrent queue исключает дедлоки - это не так, concurrent очередь не спасает от взаимного ожидания
  • отсутствие упоминания таймаутов и способов диагностики (например, DispatchQueue.main.sync с assert или os_signpost)

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью