Может ли диспетчерский семафор случайно зайти в тупик? - PullRequest
1 голос
/ 28 мая 2020

Допустим, у нас есть общий ресурс, к которому имеет доступ множество разных глобальных очередей, и, ради этого вопроса, мы используем семафор отправки для управления этим доступом. Когда одна из этих глобальных очередей сообщает семафору о необходимости ожидания, счетчик семафоров уменьшается, и этот поток получает доступ к общему ресурсу. Возможно ли, что пока семафор ожидает, другая (другая) глобальная очередь пытается получить доступ к этому общему ресурсу, и поток, который GCD захватил из своего пула, является тем же потоком, который был захвачен для предыдущей очереди (очередь, которая в настоящее время делает ожидание семафора), что заблокировало бы этот поток и предотвратило бы повторное увеличение числа семафоров?

1 Ответ

2 голосов
/ 28 мая 2020

Краткий ответ:

Да, использование семафоров может привести к тупикам, но не по той причине, которую вы предлагаете.

Длинный ответ:

Если у вас есть отправленная задача ожидая семафора, этот рабочий поток блокируется до тех пор, пока не будет получен сигнал, и он возобновит выполнение, а затем вернется. Таким образом, вам не нужно беспокоиться о том, что другая отправленная задача пытается использовать тот же поток, потому что этот поток временно удаляется из пула потоков. Вам никогда не придется беспокоиться о том, что две отправленные задачи будут пытаться использовать один и тот же поток одновременно. Это не риск взаимоблокировки.

При этом мы должны учитывать тот факт, что количество рабочих потоков в пуле потоков чрезвычайно ограничено (в настоящее время 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()

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

Я использовал только приведенный выше пример взрыва потока, потому что тупик не совсем очевиден. Но очевидно, что существует множество способов вызвать взаимоблокировки с помощью семафоров.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...