← Назад к вопросам

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

2.0 Middle🔥 131 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

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

Ключевые сценарии использования Semaphore

1. Ограничение одновременного доступа к ресурсу

Когда у вас есть ресурс, который может обслуживать ограниченное количество потребителей одновременно (например, пул соединений к базе данных, сетевые запросы к API с лимитами, доступ к оборудованию).

let maxConcurrentRequests = 3
let requestSemaphore = DispatchSemaphore(value: maxConcurrentRequests)

func performNetworkRequest(_ id: Int) {
    requestSemaphore.wait() // Ожидание, если уже 3 запроса
    defer { requestSemaphore.signal() } // Освобождение слота
    
    print("Запрос \(id) начался")
    Thread.sleep(forTimeInterval: 1)
    print("Запрос \(id) завершился")
}

// 10 задач, но выполняться будут только по 3 одновременно
DispatchQueue.concurrentPerform(iterations: 10) { i in
    performNetworkRequest(i)
}

2. Синхронизация асинхронных операций

Когда нужно дождаться завершения нескольких асинхронных задач перед продолжением выполнения (альтернатива DispatchGroup, но с более тонким контролем).

func fetchMultipleDataSources() {
    let semaphore = DispatchSemaphore(value: 0)
    var results: [String] = []
    
    // Асинхронная задача 1
    DispatchQueue.global().async {
        Thread.sleep(forTimeInterval: 0.5)
        results.append("Data from API")
        semaphore.signal()
    }
    
    // Асинхронная задача 2  
    DispatchQueue.global().async {
        Thread.sleep(forTimeInterval: 0.8)
        results.append("Data from database")
        semaphore.signal()
    }
    
    // Ожидаем завершения обеих задач
    semaphore.wait()
    semaphore.wait()
    
    print("Все данные получены: \(results)")
}

3. Реализация производитель-потребитель (Producer-Consumer)

При работе с очередями, где одни потоки производят данные, а другие потребляют их.

class Buffer {
    private let accessSemaphore = DispatchSemaphore(value: 1)
    private let itemSemaphore = DispatchSemaphore(value: 0)
    private var items: [Int] = []
    
    func produce(_ item: Int) {
        accessSemaphore.wait()
        items.append(item)
        accessSemaphore.signal()
        itemSemaphore.signal() // Уведомляем потребителя
    }
    
    func consume() -> Int {
        itemSemaphore.wait() // Ждём элемент
        accessSemaphore.wait()
        defer { accessSemaphore.signal() }
        return items.removeFirst()
    }
}

4. Контроль степени параллелимости в concurrent операциях

При использовании concurrentPerform или обработке больших массивов данных параллельно, но с ограничением максимального количества одновременно работающих потоков.

Преимущества Semaphore перед другими инструментами

  • Точный контроль количества: В отличие от DispatchGroup, который просто ожидает завершения, семафор позволяет точно ограничить количество одновременных операций
  • Гибкость: Можно реализовать сложные паттерны синхронизации
  • Низкоуровневый контроль: Более точное управление потоками по сравнению с высокоуровневыми API

Когда НЕ стоит использовать Semaphore

  1. Для простой синхронизации доступа к одной переменной — лучше использовать NSLock или os_unfair_lock
  2. Для ожидания одной асинхронной операции — проще использовать completion handlers или async/await
  3. В UI-потокеsemaphore.wait() заблокирует главный поток
  4. Для защиты от гонок данных в простых сценариях — рассмотрите @MainActor, Actors или Serial DispatchQueue

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

// ОПАСНЫЙ ПРИМЕР - может привести к дедлоку
DispatchQueue.main.async {
    let semaphore = DispatchSemaphore(value: 0)
    someAsyncOperation {
        semaphore.signal()
    }
    semaphore.wait() // Блокирует главный поток!
}

// Безопасная альтернатива
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
    someAsyncOperation {
        semaphore.signal()
    }
}
semaphore.wait() // Ожидание в фоновом потоке

Semaphore — это мощный, но "острый" инструмент. Его стоит использовать сознательно, когда нужен точный контроль над степенью параллелизма или когда другие высокоуровневые механизмы (как OperationQueue с maxConcurrentOperationCount) не обеспечивают необходимой гибкости. В современном Swift рассмотрите также использование AsyncSequence, TaskGroup и акторов как более безопасных альтернатив для многих сценариев.

Когда лучше использовать Semaphore? | PrepBro