Что произойдет, если мы вызовем тупик в параллельной очереди? - PullRequest
0 голосов
/ 05 мая 2020

Я прочитал это в документации Apple:

Важно: никогда не следует вызывать функцию dispatch_syn c или dispatch_sync_f из задачи, которая выполняется в той же очереди, в которую вы планируете передать функция. Это особенно важно для последовательных очередей, которые гарантированно входят в тупик, но этого также следует избегать для параллельных очередей.

Первый случай вызывает ошибку sh, но не второй, приложение работает. Что именно происходит в вашем вызове syn c внутри задачи, которая выполняется параллельной очередью?

let concurrentQueue = DispatchQueue(label: "queue2.concurrent", attributes: .concurrent)

    for _ in 0..<10 {
        concurrentQueue.async {
            print(Thread.current)
            concurrentQueue.sync {
                print(Thread.current)
                print(2)
            }
        }
    }

Это создает много потоков? Я не вижу этого в отладчике. И приложение не трещит sh.

1 Ответ

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

Предоставленный вами фрагмент кода не вызывает взаимоблокировки.

for l oop поместит 10 задач в очередь, а затем выполнение продолжится с конца фрагмента. Очередь будет запускать эти задачи в потоках в пуле потоков GCD, если позволяют ресурсы, возможно, создавая новые потоки, если имеется больше доступного процессорного времени, чем могут использовать существующие потоки. Каждая задача распечатает описание своего текущего потока, а затем синхронно отправит внутреннюю задачу.

Синхронная отправка внутренней задачи просто означает, что внешняя задача не будет продолжена (до конца), пока внутренняя задача не будет завершена.

Теоретически очередь может выполнять внутреннюю задачу в еще одном потоке из пула GCD. Однако, поскольку GCD знает, что вызывающий поток в противном случае заблокирован, на практике он будет запускать внутреннюю задачу прямо в вызывающем потоке. Из-за этого всегда доступен поток, и ничто не мешает немедленному запуску внутренней задачи.

Итак, ваш код ведет себя очень похоже на то, если бы внутренняя задача была просто встроена во внешнюю задачу:

for _ in 0..<10 {
    concurrentQueue.async {
        print(Thread.current)
        print(Thread.current)
        print(2)
    }
}

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

Застрявший поток потреблял бы фиксированный объем памяти и некоторые учетные данные ядра. Но это не потребляет CPU. Однако GCD имеет внутреннее ограничение на количество запускаемых потоков. Если вы заблокируете достаточное количество из них, он в конечном итоге перестанет выполнять любые асинхронные задачи.

...