Что такое семафор (Semaphore) в iOS?

Ответ

Семафор — это механизм синхронизации, который контролирует доступ ограниченного числа потоков к общему ресурсу или критической секции кода. В iOS представлен классом DispatchSemaphore из Grand Central Dispatch (GCD).

Принцип работы: Семафор содержит внутренний счетчик. Поток, вызывающий wait(), уменьшает счетчик. Если счетчик становится отрицательным, поток блокируется до тех пор, пока другой поток не вызовет signal(), увеличив счетчик.

Основные методы:

  • DispatchSemaphore(value: Int) — инициализация с начальным значением счетчика.
  • signal() — увеличивает счетчик на 1.
  • wait() — уменьшает счетчик на 1; блокирует поток, если счетчик < 0.
  • wait(timeout:) — ожидание с таймаутом.

Пример: Ограничение одновременных сетевых запросов

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

for url in urls {
    DispatchQueue.global().async {
        semaphore.wait() // Занимаем слот
        makeNetworkRequest(to: url) {
            semaphore.signal() // Освобождаем слот
        }
    }
}

Пример: Защита критической секции (аналог мьютекса)

let semaphore = DispatchSemaphore(value: 1) // Только один поток
var sharedArray = [Int]()

DispatchQueue.concurrentPerform(iterations: 100) { i in
    semaphore.wait()
    // Начало критической секции
    sharedArray.append(i)
    // Конец критической секции
    semaphore.signal()
}

Типичные сценарии использования:

  • Ограничение количества одновременных операций (например, сетевых запросов, операций ввода-вывода).
  • Синхронизация доступа к общим, изменяемым ресурсам.
  • Реализация паттернов, таких как "читатели-писатели".

⚠️ Важные замечания:

  • Deadlock: Убедитесь, что wait() и signal() вызываются парно и в правильном порядке, особенно в сложных или условных ветвлениях кода.
  • Производительность: Используйте семафоры только там, где это необходимо. Для простой синхронизации доступа к свойствам часто достаточно DispatchQueue с барьерами (barrier) или акторов (Actor).

Ответ 18+ 🔞

А, семафоры, ёпта! Ну это ж классика, как "Муму" Тургенева, только в мире многопоточности. Сейчас объясню, но без вот этого вот "Му-му", блядь.

Представь себе, сука, туалет в офисе. Один унитаз, а программистов — овердохуища. Все хотят посрать, но дверь-то одна, блядь. Вот семафор — это как сантехник с ключом, который пускает по одному. Или, если кабинок несколько, то по два, по три. Внутри у него счётчик, как табло свободных мест.

Как это работает, блядь: У сантехника (семафора) есть цифра. Ты подходишь, говоришь "wait()" — он цифру уменьшает. Если цифра ушла в минус — всё, браток, стой и жди, пока кто-то не выйдет и не крикнет "signal()". Тогда цифра подрастёт, и тебя пустят.

Основные команды, которые он понимает:

  • DispatchSemaphore(value: 3) — "Так, на три толчка рассчитано, блядь!" (инициализация).
  • wait() — "Можно я?" (занимает слот, ждёт, если нет).
  • signal() — "Я всё, выхожу!" (освобождает слот).

Пример: Не дать серверу сдохнуть от запросов Допустим, у тебя 100 URL'ов, но API твоего бэкенда — хилая жопа, и больше трёх одновременных запросов не вывозит. Делаем так:

// Сантехник на три кабинки
let semaphore = DispatchSemaphore(value: 3)

for url in urls {
    DispatchQueue.global().async {
        semaphore.wait() // Ждём своей очереди в сортир
        makeNetworkRequest(to: url) {
            // Сделал дело — гуляй смело
            semaphore.signal() // Освобождаем кабинку
        }
    }
}

Вот и всё, сервер не ляжет, как опьяневший Герасим, потому что больше трёх потоков в него не полезут одновременно.

Пример: Чтобы не наступить друг другу на мозги в общей памяти Есть массив, и 100 потоков хотят в него что-то пихнуть. Если пустить всех — будет пиздец, данные перемешаются в кашу. Делаем семафор-мьютекс (на одну кабинку):

let semaphore = DispatchSemaphore(value: 1) // Один унитаз на всех!
var sharedArray = [Int]()

DispatchQueue.concurrentPerform(iterations: 100) { i in
    semaphore.wait() // Закрылся в сортире
    // Начало критической секции (можно спокойно срать)
    sharedArray.append(i)
    // Конец критической секции
    semaphore.signal() // Открыл дверь
}

Теперь массив будет целым, а не как после пьянки.

Где это, блядь, применять:

  • Чтобы не заDDOS'ить ничего (сеть, диск, базу).
  • Когда есть общая переменная, которую все портят.
  • Для всяких хитрожопых паттернов вроде "читателей-писателей".

⚠️ А теперь, сука, внимание, главное!

  • Взаимная блокировка (deadlock): Следи, чтобы wait() и signal() всегда были парными, как носки. Если забудешь signal() — всё, твоя программа встала колом, все потоки упёрлись в закрытую дверь и ждут, а выйти некому. Пиздец и тишина.
  • Не дрочи лишнего: Не используй семафоры везде, где не надо. Для простой защиты свойства часто хватает обычной очереди с барьером (DispatchQueue.barrier) или, на новых iOS, акторов. Не усложняй, блядь, жизнь без нужды.

Вот и вся философия. Проще, чем Тургенева понять, а пользы — больше. Главное — не заблокировать всех нахуй по ошибке.