Почему dispatch_sync в настраиваемой параллельной блокировке очереди

я вижу прерывистую тупиковую ситуацию в моем приложении при использовании dispatch_sync в настраиваемом параллельном dispatch_queue. Я'м, используя что-то похожее на метод, описанный вМайк Эшблог с для поддержки одновременного доступа для чтения, но поточно-ориентированных мутаций в NSMutableDictionary, который действует как кэш активных в данный момент сетевых запросов RPC. Мой проект использует ARC.

Я создаю очередь с:

dispatch_queue_t activeRequestsQueue = dispatch_queue_create("my.queue.name",
                                                DISPATCH_QUEUE_CONCURRENT);

и изменяемый словарь с

NSMutableDictionary *activeRequests = [[NSMutable dictionary alloc] init];

Я читаю элементы из очереди следующим образом:

- (id)activeRequestForRpc: (RpcRequest *)rpc
{
    assert(![NSThread isMainThread]);
    NSString * key = [rpc getKey];
    __block id obj = nil;
    dispatch_sync(activeRequestsQueue, ^{
        obj = [activeRequests objectForKey: key];
    });
    return obj;
}

Я добавляю и удаляю rpcs из кеша '

- (void)addActiveRequest: (RpcRequest *)rpc
{
    NSString * key = [rpc getKey];
    dispatch_barrier_async(activeRequestsQueue, ^{
        [activeRequests setObject: rpc forKey: key];
    });
}

- (void)removeActiveRequest: (RpcRequest *)rpc
{
    NSString * key = [rpc getKey];
    dispatch_barrier_async(activeRequestsQueue, ^{
        [activeRequests removeObjectForKey:key];
    });
}

я вижу тупик в вызове activeRequestForRpc, когда я делаю много сетевых запросов одновременно, что наводит меня на мысль, что один из барьерных блоков (добавление или удаление) не завершает выполнение. Я всегда вызываю activeRequestForRpc из фонового потока, а пользовательский интерфейс приложения нея не замерзаюНе думаю, что это должно блокировать основной поток, но я добавил оператор assert на всякий случай. Любые идеи о том, как этот тупик может происходить?

ОБНОВЛЕНИЕ: добавление кода, который вызывает эти методы

я использую AFNetworking для сетевых запросов, и у меня есть NSOperationQueue, который яя планируюпроверить кеш и, возможно, получить ресурс из сети логика. Я'Я назову эту опцию CheckCacheAndFetchFromNetworkOp. Внутри этой операции я обращаюсь к своему пользовательскому подклассу AFHTTPClient для выполнения запроса RPC.

// this is called from inside an NSOperation executing on an NSOperationQueue.
- (void) enqueueOperation: (MY_AFHTTPRequestOperation *) op {
    NSError *error = nil;
    if ([self activeRequestForRpc:op.netRequest.rpcRequest]) {
        error = [NSError errorWithDomain:kHttpRpcErrorDomain code:HttpRpcErrorDuplicate userInfo:nil];
    }
    // set the error on the op and cancels it so dependent ops can continue.
    [op setHttpRpcError:error];

    // Maybe enqueue the op
    if (!error) {
        [self addActiveRequest:op.netRequest.rpcRequest];
        [self enqueueHTTPRequestOperation:op];
    }
}

MY_AFHTTRequestOperation создается экземпляром AFHTTPClient и содержится в блоках успешного завершения и завершения неудачного завершения, которые я вызываю[self removeActiveRequest:netRequest.rpcRequest]; как первое действие. Эти блоки выполняются в основном потоке с помощью AFNetworking в качестве поведения по умолчанию.

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

Возможно ли, что, поскольку система порождает больше потоков для поддержки операций CheckCacheAndFetchFromNetworkOp в моем NSOperationQueue, activeRequestsQueue будет иметь слишком низкий приоритет для планирования? Это может привести к взаимоблокировке, если все потоки были заняты блокировкой CheckCacheAndFetchFromNetworkOps для попытки чтения из словаря activeRequests, а activeRequestsQueue блокировал блокировку добавления / удаления барьера, которая не моглавыполнить.

ОБНОВИТЬ

Исправлена проблема, в которой для параметра NSOperationQueue было задано значение maxConcurrentOperation, равное 1 (или действительно любое другое значение, отличное от значения по умолчанию NSOperationQueueDefaultMaxConcurrentOperationCount).

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

Это то, что происходило.

очередь - NSOperationQueue установлен в значение по умолчанию NSDefaultMaxOperationCount, которое позволяет системе определять, сколько одновременных операций выполнить.

оп - работает в очереди 1 и планирует сетевой запрос в очереди AFNetworking после чтения, чтобы убедиться, что RPC неt в наборе activeRequest.

Вот поток:

Система определяет, что она может поддерживать 10 одновременных потоков (в действительности это было больше похоже на 80).

10 операций назначаются одновременно. Система позволяет одновременно запускать 10 операций.с 10 темы. Все вызовы 10 операций имеют hasActiveRequestForRPC, который планирует блок синхронизации в activeRequestQueue и блокирует 10 потоков. ActiveRequestQueue хочет запустить егочитать блок, но неНет доступных тем. На данный момент у нас уже есть тупик.

Чаще всего я вижу, что запланировано что-то вроде 9 ops (1-9), один из них, op1, быстро запускает hasActiveRequestForRPC в 10-м потоке и планирует блок-блок addActiveRequest. Затем в 10-м потоке будет запланирована другая операция, а в op2-10 будет запланировано ожидание для hasActiveRequestForRPC. Тогда op1 'запланированный блок addRpc не будетне запускается, так как op10 занял последний доступный поток, а все другие блоки hasActiveRequestForRpc будут ожидать выполнения барьерного блока. op1 блокировался бы позже, когда попытался запланировать операцию кэширования в другой очереди операций, которая также не моглаТ получить доступ к любым потокам.

Я предполагал, что блокировка hasActiveRequestForRPC ожидала выполнения на блоке barrer, но ключом было activeRequestQueue, ожидающее налюбой доступность темы.

Ответы на вопрос(1)

Ваш ответ на вопрос