Здесь я создаю параллельную очередь с приоритетом .background:

let background = DispatchQueue(label: "backgroundQueue",
                               qos: .background,
                               attributes: [],
                               autoreleaseFrequency: .inherit,
                               target: nil)

Когда я пытаюсь вызвать DispatchQueue.main.sync из этой очереди асинхронно, он выполняется успешно.

background.async {
    DispatchQueue.main.sync {
        print("Hello from background async")
    }
}

Однако, если я пытаюсь вызвать DispatchQueue.main.sync из этой очереди синхронно, это вызывает взаимоблокировку.

background.sync {
    DispatchQueue.main.sync {
        print("Hello from background sync")
    }
}

Почему асинхронный вызов DispatchQueue.main.sync из параллельной очереди завершается успешно, но синхронно не работает?

1
Rozaliia Amirova 11 Окт 2021 в 09:37

4 ответа

Лучший ответ

.sync означает, что он будет блокировать текущий рабочий поток и ждать, пока не будет выполнено закрытие. Таким образом, ваш первый .sync заблокирует основной поток (вы должны выполнять .sync в основном потоке, иначе это не будет взаимоблокировкой). И дождитесь завершения закрытия в background.sync {...}, затем можно продолжить.

Но второе закрытие блокирует фоновый поток и назначает новое задание основному потоку, который уже был заблокирован. Итак, эти два потока ждут друг друга вечно.

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


// define another background thread
let background2 = DispatchQueue(label: "backgroundQueue2",
                                       qos: .background,
                                       attributes: [],
                                       autoreleaseFrequency: .inherit,
                                       target: nil)
// don't start sample code in main thread.
background2.async {
    background.sync {
        DispatchQueue.main.sync {
            print("Hello from background sync")
        }
    }
}

Эти взаимоблокировки вызваны операцией .sync в последовательной очереди. Просто позвоните DispatchQueue.main.sync {...}, чтобы воспроизвести проблему.

// only use this could also cause the deadlock.
DispatchQueue.main.sync {
    print("Hello from background sync")
}

Или не блокируйте основной поток в самом начале, это также может разрешить тупик.

background.async {
    DispatchQueue.main.sync {
        print("Hello from background sync")
    }
}

Вывод

Операция .sync в последовательной очереди может вызвать постоянное ожидание, потому что она однопоточная. Его нельзя остановить сразу и с нетерпением жду новой работы. Работа, которую он выполняет в настоящее время, должна быть выполнена сначала, а затем может начаться другая. Вот почему .sync нельзя использовать в последовательной очереди.

1
Turtleeeeee 12 Окт 2021 в 01:34

Прежде всего, вы используете {{X0}}, но хотите {{X1}}, поэтому измените его на {{X2}}. Во-вторых, вы должны использовать {{X3}}, чтобы ваш курсор переместился в первую строку и затем получил от нее информацию.

.sync

Эта функция отправляет блок в указанную очередь отправки для синхронное исполнение. В отличие от dispatch_async (: :), эта функция выполняет не возвращаться, пока блок не закончится

Это означает, что когда вы впервые вызвали background.sync { управление было в основном потоке, который принадлежит основной очереди (которая является сериализованной очередью), как только оператор background.sync { был выполнен, управление остановилось в основной очереди и ее теперь ждем, пока блок завершит выполнение

Но внутри background.sync { вы снова обращаетесь к основной очереди, ссылаясь на DispatchQueue.main.sync { и отправляя другой блок для синхронного выполнения, который просто печатает "Hello from background sync", но элемент управления уже ожидает возврата из основной очереди из background.sync {, следовательно, вы зашли в тупик.

Основная очередь ожидает возврата управления из фоновой очереди, которая, в свою очередь, ожидает, пока основная очередь завершит выполнение оператора печати: |

На самом деле Apple специально упоминает этот вариант использования в своем описании.

Вызов этой функции и нацеливание на текущую очередь приводит к тупиковой ситуации.

Дополнительная информация:

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

       let background = DispatchQueue(label: "backgroundQueue",
                                       qos: .background,
                                       attributes: [],
                                       autoreleaseFrequency: .inherit,
                                       target: nil)
        background.sync {
            background.sync {
                print("Hello from background sync")
            }
        }

Ясно, что вы имеете в виду очередь background внутри background.sync, которая вызовет тупик, что и указано в описании документации Apple. Ваш случай немного отличался в том смысле, что вы упомянули основную очередь, косвенно вызывающую тупик.

Как использование async в любом из этих операторов нарушает работу блокировки?

Теперь вы можете использовать async либо в background.async {, либо в DispatchQueue.main.async, и взаимоблокировка существенно выйдет из строя (я не предлагаю здесь правильный вариант, это зависит от ваших потребностей и от того, что вы пытаетесь выполнить, но чтобы выйти из тупика, вы можете использовать async в любом из этих операторов отправки, и все будет в порядке)

Я просто объясню, почему тупиковая ситуация выйдет из строя только в одном сценарии (очевидно, вы можете вывести решение для другого случая). Скажем так, вы используете

        background.sync {
            DispatchQueue.main.async {
                print("Hello from background sync")
            }
        }

Теперь основная очередь ожидает завершения выполнения блока, который вы отправили в фоновую очередь для синхронного выполнения с помощью background.sync, а внутри background.sync вы снова обращаетесь к основной очереди, используя DispatchQueue.main, но на этот раз вы отправляете свой блок для асинхронного выполнения. Следовательно, управление не будет ждать, пока блок завершит выполнение, а вместо этого немедленно вернется. Поскольку в блоке, который вы отправили в фоновую очередь, нет других операторов, он отмечает завершение задачи, поэтому управление возвращается в основную очередь. Теперь основная очередь обрабатывает отправленные задачи и, когда приходит время обработать блок print("Hello from background sync"), распечатывает его.

1
Sandeep Bhandari 11 Окт 2021 в 07:43

Есть два типа DispatchQueue:

  1. Последовательная очередь - рабочий элемент начинает выполняться после завершения выполнения предыдущего.
  2. Параллельная очередь - рабочие элементы выполняются одновременно

Он также имеет два метода диспетчеризации:

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

Примечание. Попытка синхронно выполнить рабочий элемент в основной очереди приводит к тупиковой ситуации.

Для документации Apple: https://developer.apple.com/documentation/dispatch/dispatchqueue

0
Raja Chandra 11 Окт 2021 в 07:35

Прежде всего, это последовательная очередь, а не параллельная, если вам нужна параллельная очередь, вам нужно указать это в атрибутах.

Однако проблема не в этом, а в самом деле: введите описание изображения здесь

Снимок экрана взят из документации DispatchQueue, в которой, среди прочего, говорится:

Важно

Попытка синхронно выполнить рабочий элемент в основной очереди приводит к тупиковой ситуации.

Вывод: никогда не отправляйте синхронизацию в основную очередь. Рано или поздно вы попадете в тупик.

0
Cristik 11 Окт 2021 в 17:39