Что такое semaphore? Для чего он использутеся? Чем отличается от mutex?ios-141

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

Основные характеристики семафоров

  1. Счётчик: Определяет количество одновременных доступов
  2. Два основных действия:
    • wait(): Уменьшает счётчик (захватывает ресурс)
    • signal(): Увеличивает счётчик (освобождает ресурс)
  3. Блокировка потока: Если счётчик = 0, поток блокируется до освобождения ресурса

Типы семафоров

  1. Двоичный семафор (Binary Semaphore):

    • Максимальное значение = 1 (аналогичен mutex)
    • Но может быть освобождён другим потоком (в отличие от mutex)
  2. Счётный семафор (Counting Semaphore):

    • Позволяет задавать максимальное число одновременных доступов
    • Полезен для ограничения параллелизма

Реализация в iOS

import Foundation

// Создаём семафор с начальным значением 2
let semaphore = DispatchSemaphore(value: 2)

func performTask(id: Int) {
    print("Задача \(id) ждёт")
    semaphore.wait() // Уменьшаем счётчик

    defer {
        semaphore.signal() // Всегда освобождаем
        print("Задача \(id) завершена")
    }

    print("Задача \(id) выполняется")
    Thread.sleep(forTimeInterval: 1) // Имитация работы
}

for i in 1...5 {
    DispatchQueue.global().async {
        performTask(id: i)
    }
}

Вывод программы покажет, что одновременно выполняются только 2 задачи.

Основные отличия от Mutex

ХарактеристикаСемафорMutex
Владелец Нет понятия владельца Запоминает владельца
Количество потоков Может пропускать несколько Только один поток
Освобождение Любой поток может signal Только владеющий поток
Использование Ограничение параллелизма Защита shared-ресурсов

Практическое применение семафоров

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

    let networkSemaphore = DispatchSemaphore(value: 3) // Макс 3 запроса
    
  2. Синхронизация между асинхронными задачами:

    let semaphore = DispatchSemaphore(value: 0)
    
    someAsyncOperation {
        // Завершение операции
        semaphore.signal()
    }
    
    // Ожидание завершения
    semaphore.wait()
    
  3. Реализация producer-consumer:

    let itemsSemaphore = DispatchSemaphore(value: 0)
    let spaceSemaphore = DispatchSemaphore(value: bufferSize)
    

Важные нюансы использования

  1. Дедлоки: Возможны при неправильном порядке wait/signal
  2. Таймауты: Можно задавать максимальное время ожидания
    if semaphore.wait(timeout: .now() + 5) == .timedOut {
        print("Таймаут ожидания")
    }
    
  3. Производительность: Семафоры легче чем mutex в плане накладных расходов

Современные альтернативы в Swift

  1. Async/Await с TaskGroup:

    await withTaskGroup(of: Void.self) { group in
        for _ in 1...5 {
            group.addTask { await processItem() }
        }
    }
    
  2. OperationQueue с maxConcurrentOperationCount:

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 2
    

Резюмируем:

семафоры — мощный инструмент для управления доступом к ресурсам и синхронизации потоков. В отличие от mutex, они позволяют контролировать количество одновременных доступов. В iOS чаще всего используют DispatchSemaphore, но в новых проектах предпочтительнее применять современные механизмы Swift Concurrency.