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

«Что такое семафор (Semaphore) в iOS?» — вопрос из категории Многопоточность, который задают на 28% собеседований 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).