Пример использования семафора в реальной задаче
Что проверяют
Понимаете ли вы, что DispatchSemaphore это счётчик разрешений, который блокирует поток, и знаете ли, где такая блокировка недопустима. Вопрос отсеивает тех, кто прочитал про семафор в статье и несёт его в код, где он ломает приложение.
Как отвечать
Опишите механику: семафор хранит число разрешений, wait() уменьшает счётчик и блокирует поток, если разрешений не осталось, signal() возвращает разрешение обратно. Отсюда две честные задачи: ограничить число одновременно выполняемых операций и защитить ресурс, к которому нельзя обращаться параллельно.
Реалистичный пример: фоновая выгрузка файлов, когда сервер не выдерживает больше трёх загрузок сразу.
let limit = DispatchSemaphore(value: 3)
let queue = DispatchQueue(label: "upload", attributes: .concurrent)
for file in files {
queue.async {
limit.wait()
upload(file) { _ in limit.signal() }
}
}Обратите внимание собеседника на две детали: signal() стоит в колбэке, иначе разрешение вернётся до окончания загрузки и лимит перестанет работать, а сам wait() выполняется на фоновой очереди.
Дальше скажите, чем семафор заменяют. Для ограничения параллелизма подойдёт OperationQueue с maxConcurrentOperationCount, для взаимного исключения actor или последовательная очередь, для ожидания группы задач DispatchGroup или TaskGroup. Семафор остаётся там, где нужен именно счётчик разрешений поверх GCD.
Где спотыкаются
Блокируют главный поток. wait() на нём подвешивает интерфейс до самого signal(), приложение выглядит зависшим, а система вправе его убить.
Второе: превращают семафором асинхронный вызов в синхронный внутри async-функции. Кооперативный пул Swift Concurrency держит ограниченное число потоков, заблокированный поток никому не возвращается, и легко получить взаимную блокировку.
Третье: запускают сотню задач через DispatchQueue.global().async, где каждая сразу встаёт в wait(). Заблокированные потоки не освобождаются, GCD заводит новые, и вместо экономии выходит взрывной рост их числа.