Когда следует использовать Semaphore в iOS?

Ответ

Semaphore (DispatchSemaphore) используется для ограничения количества одновременных операций, обращающихся к общему ресурсу с ограниченной ёмкостью.

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

  • Rate limiting API-запросов — чтобы не превысить лимиты сервера.
  • Ограничение параллельных файловых операций или сетевых соединений.
  • Синхронизация доступа к ресурсу, когда DispatchQueue или OperationQueue недостаточно гибки.

Пример (Swift):

// Ограничиваем максимум 3 одновременные задачи
let semaphore = DispatchSemaphore(value: 3)

for i in 1...10 {
    DispatchQueue.global().async {
        semaphore.wait() // Запрашиваем доступ
        defer { semaphore.signal() } // Гарантированно освобождаем

        // Критическая секция: работа с ограниченным ресурсом
        print("Задача (i) выполняется")
        Thread.sleep(forTimeInterval: 1)
    }
}

Важные предостережения:

  1. Всегда используйте defer { semaphore.signal() } или аналогичный механизм, чтобы избежать deadlock.
  2. Никогда не вызывайте wait() на главном потоке — это заблокирует UI.
  3. Для большинства задач синхронизации предпочтительнее серийные очереди (DispatchQueue) или OperationQueue с установленным maxConcurrentOperationCount.

Ответ 18+ 🔞

А, ну вот, семафоры, блядь! Ну это ж классика, ёпта! Стоит только заикнуться про ограничение потоков — все сразу такие: «О, семафор!». Ну, типа, умные.

Смотри, в чём суть, если на пальцах. Представь себе туалет в каком-нибудь офисе, где кабинок всего, допустим, три штуки. Вот это и есть твой семафор, блядь, с value: 3. Значение — это количество свободных кабинок, проще говоря, мест.

И вот приходит народ, десять человек, и каждый хочет посрать. Первые трое заходят — всё ок. Четвёртый подходит, дергает ручку — а там занято, блядь. И стоит он, ждёт, пока кто-то не выйдет. Вот этот момент, когда он ждёт у двери — это и есть вызов semaphore.wait(). Пока не освободится место — не пускает.

А как чувак вышел из кабинки, он как бы говорит: «Я всё, свободно!». Это вызов semaphore.signal(). И наш четвёртый, который ждал, тут же заскакивает внутрь.

Вот тебе код, который эту драму и изображает. Смотри, не спутай:

// Ограничиваем максимум 3 одновременные задачи
let semaphore = DispatchSemaphore(value: 3)

for i in 1...10 {
    DispatchQueue.global().async {
        semaphore.wait() // Стоим в очереди в туалет, блядь
        defer { semaphore.signal() } // Выйдя, крикнем "СВОБОДНО!", даже если нас посреди дела вырвет

        // Критическая секция: работа с ограниченным ресурсом
        print("Задача (i) выполняется")
        Thread.sleep(forTimeInterval: 1)
    }
}

Видишь defer? Это, сука, гениальная штука. Она говорит: «Что бы ни случилось дальше в этой функции — вылет, ошибка, инопланетяне прилетели — когда мы будем из неё выходить, выполни semaphore.signal()». Это чтобы не получилось, что чувак в кабинке умер и не вышел, а все остальные стоят и ждут вечность. Deadlock, короче. Страшная хуйня.

А теперь, внимание, три главных правила, чтобы не обосраться:

  1. defer { semaphore.signal() } — твой лучший друг. Запомни это как «Отче наш». Захотел в туалет (wait) — сразу планируй, как объявишь о выходе (signal). Иначе застрянешь там навсегда, и все будут на тебя ругаться.
  2. НИКОГДА, блядь, НИКОГДА не вызывай wait() на главном потоке. Это как встать в очередь в единственный туалет и заблокировать дверь изнутри, пока ждёшь. Весь интерфейс зависнет, пользователь охуеет. Делай это только в фоновых потоках.
  3. Не умничай раньше времени. В 90% случаев, когда тебе кажется, что нужен семафор, на самом деле хватит обычной серийной очереди (DispatchQueue) или OperationQueue, где можно просто выставить maxConcurrentOperationCount. Семафор — это как кувалда, когда нужен молоток. Работает? Да. Изящно? Нет. Оверкилл, блядь.

Вот и вся магия. Вошёл — вышел. Сигнал дал. Главное — не забудь спустить воду.