Может ли диспетчерский семафор случайно зайти в тупик?

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

1 ответ

Решение

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

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

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

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

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


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

let semaphore = DispatchSemaphore(value: 0)
someAsynchronousMethod {
    // do something useful

    semaphore.signal()
}
semaphore.wait()

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

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

Другие вопросы по тегам