Шаблон проектирования Obj-C: параллельная панель запуска задач

В настоящее время у меня есть сценарий оболочки, который обрабатывает множество изображений одно за другим с помощью GraphicsMagick. Работает нормально, все расчеты правильные, все работает. (это не «простой» скрипт, он включает в себя чтение размеров из файла JSON, преобразование набора изображений с учетом многих ограничений).

Поскольку мы работаем с двухъядерным или четырехъядерным компьютером, я хотел бы распараллелить его. И поскольку я разработчик iPhone и хочу познакомиться с разработкой для Mac, я хотел бы создать его с помощью XCode и Objective-C, используя шаблон «инструмента командной строки».

Пока все хорошо, но теперь я столкнулся с дизайном объекта «диспетчер задач». Я довольно сильно теряюсь между запуском NSTasks в цикле выполнения, в отдельных потоках, с использованием блоков, с GCD или без него, с ARC или без него.

Как можно этого добиться? Я думал об использовании простых потоков для создания NSTasks, чтобы они сообщали о завершении и уведомляли делегата моего диспетчера, чтобы он мог обновить свой индикатор выполнения. Но я бы очень хотел связаться с Grand Central Dispatch. Есть у кого мысли, идеи, советы что делать, а что нет?

Изменить: я читаю документы Apple и нашел класс NSOperationQueue. Может быть, это именно то, что мне здесь нужно?


person Cyrille    schedule 06.09.2011    source источник


Ответы (3)


да - NSOperation/NSOperationQueue подходят для этой задачи.

я бы начал с чего-то вроде этого:

@protocol MONTaskRequestDelegate

- (void)taskRequestDidComplete:(MONTaskRequest *)taskRequest;

@end

@interface MONTaskRequest : NSOperation
{
@private
    NSTask * task;
    NSObject<MONTaskRequestDelegate>* delegate; /* strong reference. cleared on cancellation and completion, */
}

- (id)initWithTask:(NSTask *)task delegate:(NSObject<MONTaskRequestDelegate>*)delegate;

// interface to access the data from the task you are interested in, whether the task completed, etc.

@end

@implementation MONTaskRequest

// ...

- (void)performDelegateCallback
{
    [self.delegate taskRequestDidComplete:self];
    self.delegate = nil;
}

- (void)main
{
    NSAutoreleasePool * pool = [NSAutoreleasePool new];

    [self runTheTask];
    // grab what is needed and handle errors
    [self performDelegateCallback];

    [pool release];
}

- (void)cancel
{
    [super cancel];
    [self stopTaskIfPossible];
    [self performDelegateCallback];
}

@end

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

person justin    schedule 06.09.2011
comment
Это более или менее то, что я сделал тем временем. Действительно, NSOperationQueue — это то, что я искал (в частности, для ограничения количества активных задач). Спасибо за подтверждение! - person Cyrille; 07.09.2011
comment
Это прекрасное решение, но оно имеет один недостаток: оно запускает как поток, так и независимый процесс для каждой параллельной задачи, что увеличивает требования к ресурсам. - person aLevelOfIndirection; 07.09.2011
comment
@aLevelOfIndirection требуется независимый процесс. кроме того, NSOperationQueue делает повторное использование потоков — его реализация (10.6+) использует libdispatch (он же GCD). - person justin; 07.09.2011

Хорошим классом для запуска независимых процессов, включая параметры и переменные среды, является NSTask. Подробности смотрите в документации. Вот небольшой инструмент командной строки, который запускает 10 одновременных процессов и ждет их завершения. NSOperationQueue здесь будет избыточным, поскольку задачи уже запущены одновременно.

-- Изменить: улучшенная версия с ограниченным параллелизмом --

int main (int argc, const char * argv[])
{
   @autoreleasepool {

       // Let's not have more than 5 parallel processes
       dispatch_semaphore_t limit = dispatch_semaphore_create(5);
       dispatch_semaphore_t done  = dispatch_semaphore_create(0);

       for (int i=0;  i<10;  i++) {
           // Setup the taks as you see fit including the environment variables.
           // See docs on NSTask for more on how to use this object.
           NSTask *task = [[NSTask alloc] init];
           task.launchPath = @"/bin/ls";
           task.arguments = [NSArray arrayWithObject:@"-la"];
           task.terminationHandler = ^(NSTask *task) {
               dispatch_semaphore_signal(limit);
               if (i==9) dispatch_semaphore_signal(done);
           };

           dispatch_semaphore_wait(limit, DISPATCH_TIME_FOREVER);
           [task launch];
       }
       dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
       dispatch_release(limit);
       dispatch_release(done);
   }
   return 0;

}

-- Оригинальная версия --

int main (int argc, const char * argv[])
{
    @autoreleasepool {

        NSObject *lock = [[NSObject alloc] init];
        int __block counter = 10;

        for (int i=0;  i<10;  i++) {
            // Setup the taks as you see fit including the environment variables.
            // See docs on NSTask for more on how to use this object.
            NSTask *task = [[NSTask alloc] init];
            task.launchPath = @"/bin/ls";
            task.arguments = [NSArray arrayWithObject:@"-la"];
            task.terminationHandler = ^(NSTask *task) {
                @synchronized(lock) { counter--; }
            };
            [task launch];
        }

        while (counter)
            usleep(50);

        [lock release];
    }
    return 0;
}

В вашем случае вы можете захотеть хранить объекты NSTask в массиве для упрощения управления.

person aLevelOfIndirection    schedule 06.09.2011
comment
Это нормально, если у меня есть ограниченное количество процессов для запуска, но мне действительно нужно не запускать что-то около 500 процессов параллельно (это среднее значение, оно может достигать тысяч). Интересно для простых операций, но в этом случае мне нужно что-то более гранулированное, и NSOperationQueue кажется лучшей рабочей лошадкой. - person Cyrille; 07.09.2011
comment
Вау, семафоры... мне было интересно, как они работают с тех пор, как я впервые обнаружил API win32 около 10 лет назад! На самом деле это кажется довольно простым при чтении. Я тоже попробую посмотреть на это. Спасибо еще раз! - person Cyrille; 08.09.2011
comment
IIRC, функции dispatch_* — это функции GCD, верно? - person Cyrille; 08.09.2011
comment
@Cyrille: да, они поставляются GCD. - person aLevelOfIndirection; 08.09.2011

Я использую для этой цели [myObj PerformSelectorInBackground:@selector(doSomething) withObject:nil]; функциональность NSObject.

Идея довольно проста: вы пишете метод, который делает работу, вызываете его из основного потока с помощью вышеупомянутого метода, затем вызываете какой-нибудь селектор обратного вызова, если вам нужно как-то обработать результаты из разных потоков.

person Ibolit    schedule 06.09.2011
comment
Я знаю об этом методе, но хотел бы использовать более продвинутую форму многопоточности, например GCD или NSOperationQueue. Спасибо, в любом случае. - person Cyrille; 06.09.2011