Почему для NSOperationQueue.mainQueue.maxConcurrentOperationCount установлено значение 1

Причина этого вопроса связана с реакцией на этот вопрос.

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

Сначала небольшое предисловие и немного истории, я знаю, что NSOperation(Queue) существовала до GCD, и они были реализованы с использованием потоков до очередей отправки.

Следующее, что вам нужно понять, это то, что по умолчанию, то есть никакие «ожидающие» методы не используются для операций или очередей операций (только стандартная «addOperation:»), выполняется основной метод NSOperation. в базовой очереди NSOperationQueue асинхронно (например, dispatch_async()).

В завершение моего предисловия я ставлю под сомнение цель установки NSOperationQueue.mainQueue.maxConcurrentOperationCount в 1 в наши дни, когда базовая очередь на самом деле является основной последовательной очередью GCD (например, возврат dispatch_get_main_queue()).

Если NSOperationQueue.mainQueue уже последовательно выполняет основные методы своей операции, зачем вообще беспокоиться о maxConcurrentOperationCount?

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


person drkibitz    schedule 04.01.2018    source источник


Ответы (1)


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

Причина 1

Поскольку underlyingQueue для NSOperationQueue.mainQueue — это dispatch_get_main_queue(), что является последовательным, NSOperationQueue.mainQueue фактически является последовательным (он никогда не мог запускать более одного блока за раз, даже если его maxConcurrentOperationCount было больше 1).

Мы можем проверить это, создав собственную NSOperationQueue, поместив последовательную очередь в ее целевую цепочку underlyingQueue и установив для ее maxConcurrentOperationCount большое число.

Создайте новый проект в Xcode, используя шаблон macOS > Cocoa App с языком Objective-C. Замените реализацию AppDelegate на это:

@implementation AppDelegate {
    dispatch_queue_t concurrentQueue;
    dispatch_queue_t serialQueue;
    NSOperationQueue *operationQueue;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    concurrentQueue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
    serialQueue = dispatch_queue_create("q2", nil);
    operationQueue = [[NSOperationQueue alloc] init];

    // concurrent queue targeting serial queue
    //dispatch_set_target_queue(concurrentQueue, serialQueue);
    //operationQueue.underlyingQueue = concurrentQueue;

    // serial queue targeting concurrent queue
    dispatch_set_target_queue(serialQueue, concurrentQueue);
    operationQueue.underlyingQueue = serialQueue;

    operationQueue.maxConcurrentOperationCount = 100;

    for (int i = 0; i < 100; ++i) {
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"operation %d starting", i);
            sleep(3);
            NSLog(@"operation %d ending", i);
        }];
        [operationQueue addOperation:operation];
    }
}

@end

Если вы запустите это, вы увидите, что операция 1 не начинается до тех пор, пока не завершится операция 0, даже несмотря на то, что я установил operationQueue.maxConcurrentOperationCount на 100. Это происходит потому, что в целевой цепочке operationQueue.underlyingQueue есть последовательная очередь. Таким образом, operationQueue фактически является последовательным, хотя его maxConcurrentOperationCount не равно 1.

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

Но если вы установите operationQueue.underlyingQueue = concurrentQueue и не установите цель concurrentQueue на serialQueue, вы увидите, что 64 операции выполняются одновременно. Чтобы operationQueue выполнял операции одновременно, вся целевая цепочка, начинающаяся с ее underlyingQueue, должна быть параллельной.

Поскольку основная очередь всегда является последовательной, NSOperationQueue.mainQueue фактически всегда является последовательной.

На самом деле, если вы установите NSOperationQueue.mainQueue.maxConcurrentOperationCount в любое значение, кроме 1, это не будет иметь никакого эффекта. Если вы напечатаете NSOperationQueue.mainQueue.maxConcurrentOperationCount после попытки изменить его, вы обнаружите, что это все еще 1. Я думаю, было бы еще лучше, если бы попытка изменить его вызывала утверждение. Молчаливое игнорирование попыток изменить его, скорее всего, приведет к путанице.

Причина 2

NSOperationQueue отправляет до maxConcurrentOperationCount блоков своим underlyingQueue одновременно. Поскольку mainQueue.underlyingQueue является последовательным, одновременно может работать только один из этих блоков. После отправки этих блоков может быть слишком поздно использовать сообщение -[NSOperation cancel] для отмены соответствующих операций. Я не уверен; это деталь реализации, которую я не полностью изучил. В любом случае, если уже слишком поздно, это печально, так как это может привести к пустой трате времени и заряда батареи.

Причина 3

Как и в случае с причиной 2, NSOperationQueue отправляет до maxConcurrentOperationCount блоков своему underlyingQueue одновременно. Поскольку mainQueue.underlyingQueue является последовательным, только один из этих блоков может выполняться одновременно. Другие блоки и любые другие ресурсы, которые dispatch_queue_t использует для их отслеживания, должны бездействовать, ожидая своей очереди. Это пустая трата ресурсов. Не большая трата, но все же трата. Если для mainQueue.maxConcurrentOperationCount установлено значение 1, он будет отправлять только один блок в свой underlyingQueue за раз, тем самым предотвращая бесполезное выделение ресурсов GCD.

person rob mayoff    schedule 04.01.2018