Сколько минимум потоков нужно для дедлока?
Что проверяют
Понимаете ли вы условия взаимной блокировки или заучили число. И знаете ли, что в iOS самый частый дедлок вообще не про два конкурирующих потока, а про очередь, которая ждёт сама себя.
Как отвечать
Формально дедлок это цикл в графе ожидания, и классический случай собирается из двух потоков и двух ресурсов: первый держит A и ждёт B, второй держит B и ждёт A. Условия Коффмана стоит назвать: взаимное исключение, удержание с ожиданием, отсутствие вытеснения, круговое ожидание.
Но правильный ответ на вопрос о минимуме это один. Нерекурсивный мьютекс, захваченный дважды подряд в одном потоке, блокирует сам себя. В iOS это встречается в самой узнаваемой форме:
DispatchQueue.main.sync { // из главного потока: гарантированный дедлок
updateUI()
}То же самое с sync на последовательной очереди изнутри задачи этой же очереди. Стоит добавить и про исчерпание пула: если блокировать потоки GCD семафорами в ожидании работы, поставленной в ту же очередь, свободные потоки кончаются и встаёт всё приложение, хотя формального цикла из двух блокировок нет.
Про Swift Concurrency: await поток не блокирует, поэтому привычного дедлока на двух акторах не будет, но блокировать поток кооперативного пула семафором нельзя ровно по той же причине.
Где спотыкаются
Отвечают «нужно четыре потока», перепутав с числом условий Коффмана. Достаточно двух потоков и двух ресурсов, а с нерекурсивной блокировкой или с sync на своей очереди хватит и одного.
Предлагают лечить рекурсивной блокировкой. NSRecursiveLock снимает только самозахват в одном потоке, круговое ожидание двух потоков остаётся, и лечится оно единым порядком захвата ресурсов или отказом от вложенных блокировок.
Смешивают модели многопоточности: тянут семафор в код на Swift Concurrency, чтобы дождаться асинхронного результата. Получают заблокированный поток кооперативного пула, а при невезении и полную остановку.