dispatch_sync внутри dispatch_sync вызывает взаимоблокировку

Я только что прочитал это на objc.io полностью асинхронно но не могу найти толкового объяснения

dispatch_queue_t queueA; // assume we have this
dispatch_sync(queueA, ^(){  // (a)
    dispatch_sync(queueA, ^(){ // (b)
        foo();
    });
});

Как только мы попадаем во вторую команду dispatch_sync, мы заходим в тупик: мы не можем выполнить отправку в очередь A, потому что кто-то (текущий поток) уже находится в этой очереди и никогда не покинет ее.

пока я понимаю

  1. dispatch_sync просто добавьте рабочий элемент (я избегаю использования слова "блок", так как это может сбить с толку) в очередь A, затем этот рабочий элемент будет отправлен в целевую очередь queueA, после чего GCD сохранит поток threadWorkItem для этого рабочего элемента
  2. Когда я достигаю (b), я нахожусь в потоке threadWorkItem (предположим, что threadWorkItem — это имя этого потока), поэтому я думаю, что постановка в очередь другого рабочего элемента в очередь A невозможна. проблема. Но некоторые люди говорят, что в это время очередь A сохраняется, очередь A блокируется -> вызывает взаимоблокировку, что меня смущает.

Я уже прочитал много тем, связанных с этим, таких как Взаимоблокировка с помощью dispatch_sync, Почему мы не можем использовать dispatch_sync в текущей очереди?, Почему этот вызов dispatch_sync() зависает?, . .. но не могу найти хорошего объяснения. Кто-то говорит, что dispatch_sync блокирует очередь, кто-то говорит, что блокирует текущий поток, ... :(

Так почему же это вызывает взаимоблокировку?


person onmyway133    schedule 29.05.2014    source источник


Ответы (2)


dispatch_sync блокирует текущий поток до тех пор, пока не завершится отправленный код, и если вы синхронно выполняете диспетчеризацию из последовательной очереди, вы, следовательно, эффективно блокируете и очередь. Поэтому, если вы синхронно отправляете из последовательной очереди в себя, это приводит к взаимоблокировке.

Но чтобы было ясно, dispatch_sync блокирует текущий поток, а не текущую очередь. При работе с параллельной очередью для последующего отправленного блока будет использоваться другой рабочий поток, и взаимной блокировки не будет.

Похоже, вы отвечаете на обсуждение в конце Dispatch Queues глава Руководства по программированию с параллелизмом, в которой говорится:

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

Это не совсем правильно. Если (а) вы делаете это с параллельной очередью; и (b) есть доступные рабочие потоки, это не вызовет взаимоблокировки. Но это плохая практика, и, тем не менее, ее следует избегать.

person Rob    schedule 29.05.2014
comment
1. Таким образом, проблема возникает, если поток (который используется для вызова 1-го диспетчера_синхронизации) и поток (который будет использоваться GCD для запуска рабочего элемента для 2-го диспетчера_синхронизации) одинаковы? Вряд ли это произойдет, потому что в конечном итоге все рабочие элементы попадут в глобальные очереди, а GCD возьмет поток из пула потоков, что вряд ли делает это то же самое ?!! 2. Даже если это то же самое, я думаю, что 2-й диспетчер_синхронизации просто отправляет рабочий элемент в очередь A, и в этот момент нет задействованного потока?!! - person onmyway133; 29.05.2014
comment
в 2. Я имею в виду, что в то время рабочий элемент просто отправляется в очередь A и не выполняется немедленно, он будет запущен в будущем цикле выполнения. Так что в то время нет ни одной темы - person onmyway133; 29.05.2014
comment
в вашем втором комментарии. Для последовательной очереди dispatch_sync заблокирует поток, какой это поток? Поток, используемый для вызова 1-го диспетчера_синхронизации, или поток, в котором выполняется рабочий элемент 1-го диспетчера_синхронизации? - person onmyway133; 30.05.2014
comment
@entropy dispatch_sync всегда блокирует поток, из которого он был вызван, ожидая завершения отправленного блока кода. Если блок кода, уже отправленный в некоторую последовательную очередь, сам пытается синхронно отправить другой блок кода в ту же очередь, второй отправленный блок кода никогда не начнется, потому что он не может начаться, пока не завершится первый блок. Но этот первый блок заблокирован в результате вызова dispatch_sync. - person Rob; 30.05.2014

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

static inline void dispatch_synchronized (dispatch_queue_t queue,
                                          dispatch_block_t block)
{
    dispatch_queue_set_specific (queue, (__bridge const void *)(queue), (void *)1, NULL);
    if (dispatch_get_specific ((__bridge const void *)(queue)))
        block ();
    else
        dispatch_sync (queue, block);
}
person Dvole    schedule 06.09.2015