【问题标题】:Obj-C design pattern : parallel task launcherObj-C 设计模式:并行任务启动器
【发布时间】:2011-09-06 11:32:20
【问题描述】:

我目前有一个 shell 脚本,它在 GraphicsMagick 的帮助下一个接一个地处理许多图像。它工作正常,所有计算都是正确的,一切正常。 (这不是一个“简单”的脚本,它涉及从 JSON 文件读取尺寸,根据许多约束转换一堆图像)。

当我们使用双核或四核计算机时,我想将其并行化。由于我是一名 iPhone 开发人员,喜欢向自己介绍 Mac 开发,因此我想使用“命令行工具”模板使用 XCode 和 Objective-C 创建它。

到目前为止一切都很好,但现在我面临着“任务调度程序”对象的设计。在运行循环中运行 NSTask,在单独的线程中,使用块,有或没有 GCD,有或没有 ARC,我相当迷失。

如何实现这一目标?我正在考虑使用简单的线程来生成 NSTask,让它们在完成时报告,并通知我的调度员的委托,以便它可以升级其进度条。但我真的很想与 Grand Central Dispatch 取得联系。有没有人对做什么和不做什么有任何想法、想法和建议?

编辑:我正在阅读 Apple 的文档,并找到了 NSOperationQueue 类。难道这正是我需要的

【问题讨论】:

    标签: objective-c multithreading parallel-processing grand-central-dispatch nstask


    【解决方案1】:

    用于启动包括参数和环境变量在内的独立进程的好类是 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 对象保存在数组中以便于管理。

    【讨论】:

    • 如果我要启动的进程数量有限,那很好,但我确实需要并行启动类似 500 个进程的东西(这是平均值,它可以上升到千)。简单的操作很有趣,但在这种情况下,我需要更精细的东西,而 NSOperationQueue 似乎是这里最好的主力。
    • 哇,信号量...自从我在 10 年前第一次发现 win32 API 时就一直想知道它们是如何工作的!实际上,读起来似乎很简单。我也会试着看看这个。再次感谢!
    • IIRC,dispatch_*函数是GCD提供的吧?
    • @Cyrille:是的,它们由 GCD 提供。
    【解决方案2】:

    是的 - 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 将活动任务的数量限制在合理的范围内。

    【讨论】:

    • 这或多或少是我同时完成的。事实上,NSOperationQueue 正是我所寻找的(特别是限制活动任务的数量)。感谢您的确认!
    • 这是一个很好的解决方案,但有一个缺点:它为每个并发任务启动一个线程和一个独立进程,这增加了资源需求。
    • @aLevelOfIndirection 独立进程是必需的。此外,NSOperationQueue 确实重用线程——它的实现(10.6+)利用 libdispatch(又名 GCD)。
    【解决方案3】:

    我为此使用 [myObj performSelectorInBackground:@selector(doSomething) withObject:nil]; NSObject 的功能。

    这个想法很简单:您编写一个完成工作的方法,使用上述方法从主线程调用它,然后如果您需要以某种方式处理来自不同线程的结果,则调用一些回调选择器。

    【讨论】:

    • 我知道这种方法,但想使用一些更高级的线程形式,如 GCD 或 NSOperationQueue。还是谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 2011-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多