Что такое дедлок и как он возникает на примере serial очередей
Что проверяют
Понимаете ли вы, чем sync отличается от async и почему serial-очередь может заблокировать сама себя. Классический следующий вопрос: что будет, если вызвать DispatchQueue.main.sync из главного потока.
Как отвечать
Дедлок это состояние, при котором два участника или больше ждут друг друга и ни один не может продолжить. Для него нужно круговое ожидание: каждый держит то, что нужно другому.
Serial-очередь выполняет блоки строго по одному. sync ставит блок в очередь и блокирует вызывающий поток, пока блок не выполнится. Отсюда самый короткий дедлок:
let queue = DispatchQueue(label: "serial")
queue.async {
queue.sync { print("никогда") }
}Внешний блок занимает очередь и ждёт внутренний. Внутренний стоит в той же очереди за внешним и ждёт, пока тот закончится. Круг замкнулся. Частный случай: DispatchQueue.main.sync из главного потока. libdispatch распознаёт такой вызов и роняет приложение с сообщением dispatch_sync called on queue already owned by current thread.
Вариант с двумя очередями: блок на A синхронно ждёт B, а блок на B в это же время синхронно ждёт A.
Как избегать:
- не звать
syncна очередь, в которой вы можете уже находиться, по умолчанию выбиратьasync; - проверять контекст через
dispatchPrecondition(condition: .notOnQueue(queue)), в отладке это сразу покажет проблему; - держать единый порядок, если одна очередь всё-таки синхронно ждёт другую.
Где спотыкаются
Путают serial и concurrent. sync на глобальную concurrent-очередь изнутри неё же дедлока не даёт: такая очередь не ждёт окончания текущего блока, чтобы начать следующий.
Приводят пример с двумя потоками и двумя ресурсами без единой очереди. Это классический дедлок на мьютексах, а вопрос про GCD, и интервьюер ждёт именно sync на ту же serial-очередь.
Переносят старые привычки в Swift Concurrency и вызывают DispatchSemaphore.wait() внутри Task. Кооперативный пул потоков ограничен числом ядер, и такое ожидание может остановить его целиком. Акторы реентерабельны и классического дедлока не дают, зато состояние между await может поменяться.