Барьерные операции в NSOperationQueue

Как мы можем реализовать поведение, эквивалентное dispatch_barrier_async, используя NSOperationQueue или любую пользовательскую структуру данных, основанную на NSOperationQueue?

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

  • Небарьерные операции должны выполняться одновременно.
  • Барьерные операции должны выполняться последовательно.

NB: Не используйте GCD, так как он не предоставляет (или, по крайней мере, затруднен) большого доступа к операциям, например отмену одной операции и т. д.


person TheCommonEngineer    schedule 19.03.2014    source источник
comment
Вы также не можете отменить NSOperation, если он уже запущен.   -  person Cy-4AH    schedule 19.03.2014
comment
@ Cy-4AH Я не собираюсь отменять какие-либо запущенные NSOperation, и диспетчера dispatch_barrier_async этого не делает. Барьерная функция будет ждать завершения предыдущих операций.   -  person TheCommonEngineer    schedule 19.03.2014


Ответы (4)


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

Я создаю NSMutableArray из NSOperationQueues, которые служат «очередью очередей». Каждый раз, когда вы добавляете объект BarrierOperation, вы добавляете новую приостановленную очередь операций в конце. Это становится addingQueue, к которому вы добавляете последующие операции.

- (void)addOperation:(NSOperation *)op {
    @synchronized (self) {
        if ([op isKindOfClass:[BarrierOperation class]]) {
            [self addBarrierOperation:(id)op];
        } else {
            [[self addingQueue] addOperation:op];
        }
    }
}

// call only from @synchronized block in -addOperation:
- (void)addBarrierOperation:(BarrierOperation *)barrierOp {
    [[self addingQueue] setSuspended:YES];

    for (NSOperation *op in [[self addingQueue] operations]) {
        [barrierOp addDependency:op];
    }

    [[self addingQueue] addOperation:barrierOp];

    // if you are free to set barrierOp.completionBlock, you could skip popCallback and do that

    __block typeof(self) weakSelf = self;
    NSOperation *popCallback = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf popQueue];
    }];
    [popCallback addDependency:barrierOp];
    [[self addingQueue] addOperation:popCallback];
    [[self addingQueue] setSuspended:NO];

    NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
    [opQueue setSuspended:YES];

    [_queueOfQueues addObject:opQueue]; // fresh empty queue to add to
}

Когда один NSOperationQueue заканчивается, он выскакивает, и начинает работать следующий.

- (void)popQueue
{
    @synchronized (self) {
        NSAssert([_queueOfQueues count], @"should always be one to pop");
        [_queueOfQueues removeObjectAtIndex:0];

        if ([_queueOfQueues count]) {
            // first queue is always running, all others suspended
            [(NSOperationQueue *)_queueOfQueues[0] setSuspended:NO];
        }
    }
}

Я мог пропустить что-то важное. Дьявол кроется в деталях.

Для меня это немного пахнет домашним заданием. Если да, то скажите мне, какую оценку я получаю. :)


Приложение: через комментарий abhilash1912 другой, но похожий подход. Этот код проверен, поэтому он уже побеждает. Но он немного устарел (2 года или около того на сегодняшний день; использование некоторых устаревших методов). Более того, я сомневаюсь, является ли наследование от NSOperationQueue лучшим путем, хотя он имеет преимущество в сохранении знакомства. В любом случае, если вы дочитали до этого места, возможно, вам стоит перечитать.

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

person Clay Bridges    schedule 19.03.2014
comment
Нет, это не было заданием, так что оценки для вас. :П. Это было частью проекта моей компании, где я хотел заменить GCD на NSOperationQueue, чтобы получить больше гибкости и управляемости. Что ж, у меня была идея реализовать это с помощью зависимостей, но я просто хотел убедиться, что это лучший из возможных способов, и мне нужно было более чистое решение, которое действительно предоставил ваш ответ. :) - person TheCommonEngineer; 19.03.2014
comment
О, подождите, я понял. Снят. - person iluvcapra; 20.03.2014
comment
Кстати, здесь я нашел еще несколько альтернатив. Комментарии Даниэля к this кажутся весьма полезными в этом контексте. - person TheCommonEngineer; 20.03.2014

Создайте NSOperation, который будет вашим барьером, затем использовать:

- (void)addDependency:(NSOperation *)operation

Чтобы сделать эту работу барьера зависимой от всех тех, которые вы хотите предшествовать ей.

person i_am_jorf    schedule 19.03.2014
comment
Это удовлетворит только половину моих требований. Но вроде понял. Спасибо. :) - person TheCommonEngineer; 19.03.2014

Я не думаю, что возможно создать объект NSOperation, который дает вам ту же функциональность, барьеры больше связаны с тем, как работает очередь.

Основное различие между использованием барьера и механизма зависимостей NSOperations заключается в том, что в случае барьера очередь потоков ожидает завершения всех запущенных параллельных операций, а затем запускает блок барьера, в то время как убедившись, что любые новые отправленные блоки и любые ожидающие блоки не запускаются до тех пор, пока критический блок не будет пройден.

С NSOperationQueue невозможно настроить очередь таким образом, чтобы она обеспечивала надлежащий барьер: все NSOperation, добавленные в очередь до вашего критического NSOperation, должны быть явно зарегистрированы как зависимость с критическим заданием, и как только критическое задание запущено, вы должны явным образом защитить NSOperationQueue, чтобы убедиться, что другие клиенты не передают на него задания до завершения критического задания; вы защищаете очередь, добавляя критическое задание в качестве зависимости для последующих операций.

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

Можно было бы получить этот уровень функциональности, настроив NSOperationQueue с максимальным одновременным заданием, но я думаю, что это противоречит цели этого. Вы также можете выполнить эту работу, заключив NSOperationQueue в фасадный объект, защищающий NSOperations, отправленный "критически".

person iluvcapra    schedule 19.03.2014
comment
Это более или менее то, о чем я думал, но ответ Клея более поясняющий. И, конечно же, он не может реализовать именно то, что GCD делает с барьерной операцией. В любом случае спасибо за хорошее объяснение. :) - person TheCommonEngineer; 19.03.2014

Еще один способ... не делай мне больно.

Todo: сохранить завершение источника, а self.maxConcurrentOperationCount = 1 устанавливает последовательную очередь при добавлении. Но следует перед казнью.

#import "NSOperationQueue+BarrierOperation.h"

@implementation NSOperationQueue (BarrierOperation)

- (void)addOperationAsBarrier:(NSOperation *)op
{
    //TODO: needs to save origin completion
    //    if (op.completionBlock)
    //    {
    //        originBlock = op.completionBlock;
    //    }

    NSOperationQueue* qInternal = [NSOperationQueue new];
    NSInteger oldMaxConcurrentOperationCount = self.maxConcurrentOperationCount;

    op.completionBlock = ^{
        self.maxConcurrentOperationCount = oldMaxConcurrentOperationCount;
        NSLog(@"addOperationAsBarrier maxConcurrentOperationCount restored");
    };

    [self addOperationWithBlock:^{
        self.maxConcurrentOperationCount = 1;
        NSLog(@"addOperationAsBarrier maxConcurrentOperationCount = 1");
    }];

    [qInternal addOperationWithBlock:^{
        NSLog(@"waitUntilAllOperationsAreFinished...");
        [self waitUntilAllOperationsAreFinished];
    }];

    NSLog(@"added OperationAsBarrier");
    [self addOperation:op];
}

@end
person Mike Glukhov    schedule 11.03.2015