dispatch_queue_set_specific по сравнению с получением текущей очереди

Я пытаюсь понять разницу и использование между этими двумя:

static void *myFirstQueue = "firstThread";

dispatch_queue_t firstQueue = dispatch_queue_create("com.year.new.happy", DISPATCH_QUEUE_CONCURRENT);

dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

Вопрос 1

В чем разница между этим:

dispatch_sync(firstQueue, ^{

    if(dispatch_get_specific(myFirstQueue))
    {
        //do something here
    }
});

и следующее:

dispatch_sync(firstQueue, ^{

    if(firstQueue == dispatch_get_current_queue())
    {
       //do something here
    }
});

?

Вопрос 2.

Вместо использования вышеуказанного (void*) myFirstQueue в

dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

Можем ли мы использовать static int * myFirstQueue = 0; вместо этого?

Мои рассуждения основаны на том, что:

dispatch_once_t тоже 0 (есть ли тут какая-то корреляция? Кстати, я до сих пор не совсем понимаю, почему dispatch_once_t надо инициализировать 0, хотя я уже читал здесь вопросы по SO).

Вопрос №3

Можете ли вы привести здесь пример GCD Deadlock?

Вопрос №4

Это может быть слишком много, чтобы просить; Я все равно спрошу, если вдруг кто-то знает это навскидку. Если нет, то было бы нормально оставить эту часть без ответа.

Я не пробовал это, потому что я действительно не знаю, как это сделать. Но моя концепция такова:

Можем ли мы в любом случае «поместить дескриптор» в какую-либо очередь, что позволит нам по-прежнему удерживать дескриптор для нее и, таким образом, иметь возможность обнаруживать, когда возникает взаимоблокировка после отделения очереди; и когда есть, и поскольку мы получили дескриптор очереди, которую мы ранее установили, мы могли бы как-то сделать что-то, чтобы разблокировать тупик?

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

С новым годом.


@san.t

С static void *myFirstQueue = 0;

Мы делаем это:

dispatch_queue_set_specific(firstQueue, &myFirstQueue, &myFirstQueue, NULL);

Совершенно понятно.

Но если мы сделаем:

static void *myFirstQueue = 1; 
//or any other number other than 0, it would be OK to revert back to the following?
dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

Относительно dispatch_once_t:

Не могли бы вы подробнее рассказать об этом:

Почему dispatch_once_t сначала должно быть 0 и как и почему оно должно действовать как логическое значение на более позднем этапе? Это связано с памятью/безопасностью или с тем, что предыдущий адрес памяти был занят другими объектами, не равными 0 (nil)?

Что касается вопроса № 3:

Извините, возможно, я не совсем ясен: я не имел в виду, что у меня тупиковая ситуация. Я имел в виду, может ли кто-нибудь показать мне сценарий в коде с GCD, который приводит к взаимоблокировке.

Наконец:

Надеюсь, вы смогли ответить на вопрос № 4. Если нет, как упоминалось ранее, все в порядке.


person Unheilig    schedule 31.12.2013    source источник
comment
dispatch_get_current_queue устарел. Было показано, что в его основе лежат серьезные проблемы. Это небезопасно. Никогда не используйте его.   -  person matt    schedule 31.12.2013
comment
Я не понимаю, почему вы проверяете, в какой очереди вы находитесь, сразу после того, как вы явно отправили блок в эту очередь. Вам нужна такая проверка перед отправкой, а не после, как я пытался сделать это в своей вспомогательной функции в этом вопросе: stackoverflow.com/questions/12806506/ . Возможно, вы захотите прочитать ответ Роба и некоторые комментарии под ним, чтобы узнать о некоторых тонких различиях между устаревшими dispatch_get_current_queue() и dispatch_get_specific().   -  person Brad Larson    schedule 31.12.2013
comment
@BradLarson На самом деле я этого не делаю; это просто служит примером для меня, чтобы задать вопрос. Спасибо за ссылку; обязательно прочитаю.   -  person Unheilig    schedule 31.12.2013
comment
@Unheilig В одном из своих видео на WWDC Apple очень четко объяснила, почему они устарели dispatch_get_current_queue. Детали сложные. В конце концов, это многопоточность на уровне ядра. Но дело в том, что это конкретное осуждение — не просто служебное предложение. Это серьезно.   -  person matt    schedule 01.01.2014
comment
Я думаю, что отмахиваться от проблемы, поскольку она сложная, оказывает программистам медвежью услугу. Концептуальные недостатки рекурсивной блокировки действительно малозаметны, но их понимание проливает массу света на то, как эффективно выполнять параллельное программирование.   -  person Catfish_Man    schedule 01.01.2014
comment
Не нужно отмахиваться - бесполезность dispatch_get_current_queue легко объяснима: нет однозначного ответа на вопрос, в какой очереди я сейчас работаю? в общем случае. Очереди отправки — это иерархия, которая заканчивается одной из множества непрозрачных глобальных очередей. Также могут быть произвольные иерархии очередей, построенные с использованием dispatch_set_target_queue.. Если вы хотите знать, находитесь ли вы в основной очереди, используйте [NSThread isMainThread]. Если вы пытаетесь использовать очереди для реализации рекурсивных блокировок, просто остановитесь. Очереди и блокировки - разные вещи.   -  person ipmcc    schedule 01.01.2014
comment
FWIW, я подробно объяснил dispatch_get/set_specific здесь: stackoverflow.com/questions/19833744/ Я также подробно описал требования к нулевости dispatch_once_t здесь: stackoverflow.com/questions/19832150/   -  person ipmcc    schedule 01.01.2014


Ответы (2)


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

Есть две фундаментальные проблемы с использованием dispatch_get_current_queue() таким образом. Один очень общий, который можно резюмировать как «рекурсивная блокировка - плохая идея», и один, относящийся к диспетчеризации, который можно резюмировать как «вы можете и часто будете иметь более одной текущей очереди».

Проблема №1: рекурсивная блокировка — плохая идея

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

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

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

Если внутри блока вы вызываете что-то, что пытается использовать вещь, защищенную очередью, то вы меняете это простое правило на гораздо более сложное: вместо «в начале и в конце каждого блока» это « в начале, конце и в любой точке, где этот блок вызывает что-то вне себя". Другими словами, вместо того, чтобы думать о потокобезопасности на уровне блоков, теперь вам нужно проверять каждую отдельную строку каждого блока.

Какое это имеет отношение к dispatch_get_current_queue()? Единственная причина использовать здесь dispatch_get_current_queue() — это проверить «мы уже в этой очереди?», а если вы уже в текущей очереди, то вы уже находитесь в страшной ситуации, описанной выше! Так что не делай этого. Используйте частные очереди для защиты вещей и не вызывайте другой код изнутри них. Вы уже должны знать ответ на вопрос «Я в этой очереди?» а должно быть "нет".

Это основная причина, по которой dispatch_get_current_queue() устарел: чтобы люди не пытались имитировать с его помощью рекурсивную блокировку (то, что я описал выше).

Проблема №2: у вас может быть несколько текущих очередей!

Рассмотрим этот код:

dispatch_async(queueA, ^{
    dispatch_sync(queueB, ^{
        //what is the current queue here?
    });
});

Очевидно, что очередь B текущая, но мы все еще находимся в очереди A! dispatch_sync заставляет работу в очереди A ожидать завершения работы в очереди B, так что они оба фактически являются "текущими".

Это означает, что этот код заблокируется:

dispatch_async(queueA, ^{
    dispatch_sync(queueB, ^{
        dispatch_sync(queueA, ^{});
    });
});

Вы также можете иметь несколько текущих очередей, используя целевые очереди:

dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
    dispatch_sync(queueA, ^{ /* deadlock! */ });
});

Что действительно нужно здесь, так это что-то вроде гипотетического "dispatch_queue_is_synchronous_with_queue(queueA, queueB)", но поскольку это будет полезно только для реализации рекурсивной блокировки, а я уже описал, насколько это плохая идея... вряд ли это будет добавлено.

Обратите внимание, что если вы используете только dispatch_async(), вы невосприимчивы к взаимоблокировкам. К сожалению, вы вовсе не застрахованы от условий гонки.

person Catfish_Man    schedule 31.12.2013
comment
Кроме того, в вашем № 2, если queueB цели queueA, есть не только тупик, но и вопрос о том, в какой очереди я нахожусь? еще более неоднозначно, чем уже было. Спасибо за хорошую борьбу за прекращение попыток реализации рекурсивных блокировок с помощью очередей GCD. Надеюсь, если это будет пересказано достаточное количество раз и в достаточном количестве мест, люди перестанут пытаться это сделать. (Мне приходит в голову, что recursive-locks-using-GCD вроде как сломана версия блокировки с двойной проверкой этого поколения. Для справки: cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html ) - person ipmcc; 01.01.2014
comment
Я думаю, что на самом деле вы можете упустить мою мысль, ipmcc :) Я утверждаю, что рекурсивные блокировки - это проблема, и точка. Невозможно реализовать их с помощью части GCD, это скорее побочная проблема, хотя и совершенно верно. - person Catfish_Man; 01.01.2014
comment
Я согласен с вами, но эта дискуссия в конечном итоге сводится к функциональному программированию/неизменяемому состоянию, а сегодня это просто невыполнимая идиома для многих работающих разработчиков. Рекурсивные блокировки с использованием GCD возникают здесь довольно часто, и это практическая проблема, которую можно было бы полностью решить, если бы мы могли просто получить достаточное количество разных версий, чтобы можно было закрывать любые новые версии. обманщики - person ipmcc; 01.01.2014
comment
Не соглашусь по поводу двусмысленности. Текущая очередь в любой ситуации очень четкая. В первом примере текущая очередь — это очередь B, потому что ваш код выполняется в очереди B и только в очереди B. Тот факт, что очередь A заблокирована в ожидании очереди B, не имеет значения. Если я вызову executeSelectorOnMainThread, вы не скажете, что код внутри выполняется в потоке 39 только потому, что поток 39 заблокирован в ожидании выполнения кода в основном потоке. Ошибка заключается в предположении, что очереди всегда независимы. Как только вы вызываете dispatch_sync, это предположение нарушается. Не делай этого. :-) - person dgatwood; 08.05.2015
comment
Кроме того, ваше утверждение в первой части IMO неверно. Вам не нужно гарантировать инвариант при вызове других методов, если эти методы не зависят от истинности этого инварианта или являются частью создания/нарушения инварианта. Совершенно разумно использовать последовательную очередь так же, как поток — для предотвращения параллелизма в фрагменте кода произвольной сложности. В таких ситуациях имеет смысл запросить текущую очередь, чтобы отловить ошибки программиста, такие как вызов одного из этих методов откуда-то вне очереди, потому что такие ошибки могут вызвать непредвиденный параллелизм. - person dgatwood; 08.05.2015

Вопрос 1. Два фрагмента кода делают одно и то же, то есть "работают", когда блок действительно работает в firstQueue. Однако они используют разные способы определения того, что он работает на firstQueue, первый устанавливает не NULL контекст ((void*)myFirstQueue) с определенным ключом (myFirstQueue), а затем проверяет, что контекст действительно не NULL; вторая проверяет с помощью уже устаревшей функции dispatch_get_current_queue. Предпочтительнее первый способ. Но тогда мне это кажется излишним, dispatch_sync гарантирует запуск блока уже в firstQueue.

Вопрос 2: просто использовать static int * myFirstQueue = 0; нельзя, таким образом, myFirstQueue является указателем NULL, а dispatch_queue_set_specific(firstQueue, key, context, NULL); требует, чтобы для работы не использовались NULL key и context. Однако он будет работать с небольшими изменениями, такими как:

static void *myFirstQueue = 0;
dispatch_queue_t firstQueue = dispatch_queue_create("com.year.new.happy", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_set_specific(firstQueue, &myFirstQueue, &myFirstQueue, NULL);

это будет использовать адрес переменной myFirstQueue в качестве ключа и контекста.

If we do:

static void *myFirstQueue = 1; 
//or any other number other than 0, it would be OK to revert back to the following?
dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

Я думаю, все будет хорошо, так как оба указателя myFirstQueue не будут разыменованы, если последний параметр destructor равен NULL

dispatch_once_t также 0 не имеет к этому никакого отношения. Сначала он равен 0, и после того, как он будет отправлен один раз, его значение изменится на ненулевое, по существу действуя как логическое значение.

Вот выдержки из once.h, вы можете видеть, что dispatch_once_t на самом деле является long и что детали реализации Apple требуют, чтобы оно было изначально 0, вероятно, потому, что статические и глобальные переменные по умолчанию равны нулю. И вы можете видеть, что есть строка:

if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {

по существу проверка once predicate по-прежнему равна нулю перед вызовом функции dispatch_once. Это не связано с безопасностью памяти.

/*!
 * @typedef dispatch_once_t
 *
 * @abstract
 * A predicate for use with dispatch_once(). It must be initialized to zero.
 * Note: static and global variables default to zero.
 */
typedef long dispatch_once_t;

/*!
 * @function dispatch_once
 *
 * @abstract
 * Execute a block once and only once.
 *
 * @param predicate
 * A pointer to a dispatch_once_t that is used to test whether the block has
 * completed or not.
 *
 * @param block
 * The block to execute once.
 *
 * @discussion
 * Always call dispatch_once() before using or testing any variables that are
 * initialized by the block.
 */
#ifdef __BLOCKS__
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
_dispatch_once(dispatch_once_t *predicate, dispatch_block_t block)
{
    if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
        dispatch_once(predicate, block);
    }
}
#undef dispatch_once
#define dispatch_once _dispatch_once
#endif

Вопрос 3: если myQueue является последовательной, параллельные очереди подходят.

dispatch_async(myQueue, ^{
    dispatch_sync(myQueue, ^{
        NSLog(@"This would be a deadlock");
    });
});

Вопрос 4. Не уверен.

person san.t    schedule 31.12.2013