dispatch_queue_set_specific срещу получаване на текущата опашка

Опитвам се да разбера разликата и употребата между тези 2:

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 Apple беше много ясен в един от техните видеоклипове на WWDC защо са отхвърлили 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?
    });
});

Ясно е, че queueB е актуален, но също така все още сме на queueA! dispatch_sync кара работата по queueA да чака завършването на работата по queueB, така че и двете са ефективно "актуални".

Това означава, че този код ще блокира:

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, if queueB targets 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, е без значение. Ако извикам performSelectorOnMainThread, няма да кажете, че кодът вътре се изпълнява в нишка 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