Как реализовать реентерабельный механизм блокировки в Objective-C через GCD?

У меня есть класс target-c с некоторыми методами, которые используют очередь GCD, чтобы обеспечить последовательный одновременный доступ к ресурсу (стандартный способ сделать это).

Некоторые из этих методов должны вызывать другие методы того же класса. Таким образом, механизм блокировки должен быть повторно введен. Есть ли стандартный способ сделать это?

Сначала я использовал каждый из этих методов

dispatch_sync(my_queue, ^{

   // Critical section

});

синхронизировать доступы. Как вы знаете, когда один из этих методов вызывает другой такой же метод, возникает взаимоблокировка, потому что вызов dispatch_sync останавливает текущее выполнение до тех пор, пока не будет выполнен другой блок, который также не может быть выполнен, поскольку выполнение в очереди остановлено. Чтобы решить эту проблему, я использовал, например. Этот метод:

- (void) executeOnQueueSync:(dispatch_queue_t)queue : (void (^)(void))theBlock {
    if (dispatch_get_current_queue() == queue) {
        theBlock();
    } else {
        dispatch_sync(queue, theBlock);
    }
}

И в каждом из моих методов я использую

[self executeOnQueueSync:my_queue : ^{

   // Critical section

}];

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


person Daniel S.    schedule 21.10.2013    source источник
comment
Думали ли вы вместо этого использовать @synchronized?   -  person Martin R    schedule 21.10.2013
comment
@MartinR, да, но @synchronized - это классическая блокировка, не основанная на GCD/очередях, и поэтому, насколько я понимаю, ее не рекомендуется использовать из соображений простоты кода и производительности. Таким образом, заголовок этого вопроса вводит в заблуждение, поскольку содержит блокировку. Я имею в виду синхронизацию доступа реентерабельным способом с GDC/очередями. У меня просто не было лучших слов, чем «реентерабельная блокировка», потому что именно так называется решение проблемы, известное большинству людей.   -  person Daniel S.    schedule 21.10.2013
comment
@MartinR, более того, хотя @synchronized имеет то преимущество, что приводит к более простому коду для реентерабельного -независимо- (как это лучше назвать?). Тем не менее, механизм блокировки, стоящий за ним, имеет меньшую производительность, чем механизм, лежащий в основе очередей GCD.   -  person Daniel S.    schedule 21.10.2013


Ответы (1)


Перво-наперво: dispatch_get_current_queue() устарело. Каноническим подходом теперь будет использование dispatch_queue_set_specific. Один из таких примеров может выглядеть так:

typedef dispatch_queue_t dispatch_recursive_queue_t;
static const void * const RecursiveKey = (const void*)&RecursiveKey;

dispatch_recursive_queue_t dispatch_queue_create_recursive_serial(const char * name)
{
    dispatch_queue_t queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL);
    dispatch_queue_set_specific(queue, RecursiveKey, (__bridge void *)(queue), NULL);
    return queue;
}

void dispatch_sync_recursive(dispatch_recursive_queue_t queue, dispatch_block_t block)
{
    if (dispatch_get_specific(RecursiveKey) == (__bridge void *)(queue))
        block();
    else
        dispatch_sync(queue, block);
}

Этот шаблон вполне пригоден для использования, но, возможно, он не является пуленепробиваемым, потому что вы можете создавать вложенные рекурсивные очереди с помощью dispatch_set_target_queue, и попытка поставить работу во внешней очереди из внутренней приведет к взаимоблокировке, даже если вы уже находитесь «внутри блокировки» ( в насмешливых кавычках, потому что он только выглядит как замок, на самом деле это нечто другое: очередь — отсюда и вопрос, верно?) для внешнего. (Вы можете обойти это, оборачивая вызовы в dispatch_set_target_queue и поддерживая свой собственный внеполосный график таргетинга и т. д., но это остается в качестве упражнения для читателя.)

Вы продолжаете говорить:

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

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

person ipmcc    schedule 21.10.2013
comment
Спасибо за объяснение устаревшей функции и того, что использовать вместо нее и как. Одно это многого стоит. - person Daniel S.; 21.10.2013
comment
@ipmcc, я действительно впечатлен вашими ответами и комментариями как к этой, так и к связанным темам. Не могли бы вы также взглянуть на этот вопрос Я только что написал? - person Stanislav Pankevich; 25.11.2013
comment
@ipmcc Есть ли разница в эффективности или результативности этого подхода по сравнению с использованием очереди с меткой & dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)? - person Orangenhain; 27.02.2017
comment
Метка имеет внутреннее семантическое значение (удобочитаемая строка, описывающая или идентифицирующая очередь). Специальный слот данных существует специально для того, чтобы означать «все, что вы хотите». Если бы вы собирались выбрать один из них для создания системы блокировки с повторным входом (‹-- насмешливые кавычки), предпочтительным оружием было бы конкретное. - person ipmcc; 09.03.2017