Ответ
Семафор — это примитив синхронизации, который контролирует доступ к общему ресурсу несколькими потоками. Он основан на счетчике, определяющем, сколько потоков могут одновременно войти в критическую секцию. В iOS используется DispatchSemaphore из Grand Central Dispatch (GCD).
Принцип работы:
- Семафор инициализируется начальным значением счетчика (например,
1). - Поток вызывает
wait(), что уменьшает счетчик. Если счетчик становится отрицательным, поток блокируется до его увеличения. - Поток вызывает
signal()после завершения работы с ресурсом, что увеличивает счетчик и может разбудить ожидающий поток.
Пример: Ограничение доступа к общему ресурсу
import Foundation
// Общий ресурс (например, массив)
var sharedArray = [Int]()
let semaphore = DispatchSemaphore(value: 1) // Разрешаем доступ только одному потоку
// Поток 1
DispatchQueue.global().async {
for i in 1...5 {
semaphore.wait() // Захватываем семафор (уменьшаем счетчик с 1 до 0)
sharedArray.append(i)
print("Поток 1 добавил: (i)")
semaphore.signal() // Освобождаем семафор (увеличиваем счетчик обратно до 1)
}
}
// Поток 2
DispatchQueue.global().async {
for i in 6...10 {
semaphore.wait() // Будет ждать, если Поток 1 еще в критической секции
sharedArray.append(i)
print("Поток 2 добавил: (i)")
semaphore.signal()
}
}
// Без семафора вывод мог бы перемешаться, а массив мог бы оказаться в поврежденном состоянии.
Другие сценарии использования:
- Ограничение количества одновременных сетевых запросов: Инициализировать семафор со значением
N(макс. число запросов). - Ожидание завершения асинхронных задач: Создать семафор со значением
0и вызыватьsignal()по завершению задачи, а основной поток будет ждать черезwait(). - Реализация мьютекса (Mutex): Семафор со значением
1действует как мьютекс, обеспечивая взаимоисключение (mutual exclusion).
Важные замечания:
- Взаимоблокировка (Deadlock): Риск возникает, если поток, захвативший семафор, попытается захватить его снова (рекурсивно) без предварительного освобождения. Для рекурсивных блокировок используйте
NSRecursiveLock. - Приоритет инверсии: Низкоприоритетный поток, захвативший семафор, может блокировать высокоприоритетный.
- Альтернативы: Для многих случаев синхронизации проще и безопаснее использовать очереди (DispatchQueue) с барьерами (
barrier), акторы (Actors) в Swift Concurrency или готовые потокобезопасные коллекции.