Краткий ответ:
Да, использование семафоров может привести к тупикам, но не по той причине, которую вы предлагаете.
Длинный ответ:
Если у вас есть отправленная задача ожидая семафора, этот рабочий поток блокируется до тех пор, пока не будет получен сигнал, и он возобновит выполнение, а затем вернется. Таким образом, вам не нужно беспокоиться о том, что другая отправленная задача пытается использовать тот же поток, потому что этот поток временно удаляется из пула потоков. Вам никогда не придется беспокоиться о том, что две отправленные задачи будут пытаться использовать один и тот же поток одновременно. Это не риск взаимоблокировки.
При этом мы должны учитывать тот факт, что количество рабочих потоков в пуле потоков чрезвычайно ограничено (в настоящее время 64 на QoS). Если вы исчерпаете все доступные рабочие потоки, то все остальное, отправленное в GCD (с таким же QoS), не сможет работать, пока некоторые из ранее заблокированных рабочих потоков не станут снова доступными.
Учитывайте:
print("start")
let semaphore = DispatchSemaphore(value: 0)
let queue = DispatchQueue.global()
let group = DispatchGroup()
let count = 10
for _ in 0 ..< count {
queue.async(group: group) {
semaphore.wait()
}
}
for _ in 0 ..< count {
queue.async(group: group) {
semaphore.signal()
}
}
group.notify(queue: .main) {
print("done")
}
Работает нормально. У вас есть десять рабочих потоков, связанных с этими вызовами wait
, а затем дополнительные десять отправленных блоков вызывают signal
, и все в порядке.
Но если вы увеличите count
до 100 (условие называемый «взрывом потока»), приведенный выше код никогда не разрешится сам, потому что вызовы signal
ожидают рабочих потоков, которые связаны со всеми этими вызовами wait
. Ни одна из этих отправленных задач с вызовами signal
никогда не будет запущена. И, когда вы исчерпываете рабочие потоки, это обычно является катастрофической проблемой c, потому что все, что пытается использовать GCD (для того же QoS), не сможет работать.
Кстати, использование семафоров в сценарии взрыва потока - лишь один из способов вызвать тупик. Но для полноты картины стоит отметить, что существует множество способов выйти из тупика с помощью семафоров. Наиболее распространенный пример - это когда семафор (или группа диспетчеризации, или что-то еще) используется для ожидания некоторого асинхронного процесса, например,
let semaphore = DispatchSemaphore(value: 0)
someAsynchronousMethod {
// do something useful
semaphore.signal()
}
semaphore.wait()
Это может привести к взаимной блокировке, если (а) вы запустите его из основной очереди; но (б) асинхронный метод также вызывает свой обработчик завершения в основной очереди. Это прототипный тупик семафора.
Я использовал только приведенный выше пример взрыва потока, потому что тупик не совсем очевиден. Но очевидно, что существует множество способов вызвать взаимоблокировки с помощью семафоров.