当所有正在运行的任务都在等待时,GCD死锁,不会启动待处理的任务

时间:2015-03-31 17:03:21

标签: objective-c multithreading

我有一个递归函数,可以在并发队列上调度新任务。我想限制同时安排的任务的数量,所以我使用信号量,以便每个任务都等待它,直到较旧的线程结束并发信号通知信号。

但是,当达到最大运行线程数(64)并且它们都开始等待信号量时,我发现队列会死锁。然后GCD即使在其待处理队列中有足够的任务也不会启动新任务。

我做错了什么?这是我的代码:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 
{
    dispatch_semaphore_t sem = dispatch_semaphore_create(10);

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
               {
                   [self recurWithSemaphore:sem];
               });
}

- (void)recurWithSemaphore:(dispatch_semaphore_t)sem
{
    // do some lengthy work here...

    // at this point we're done all but scheduling new tasks so let new tasks be created
    dispatch_semaphore_signal(sem);

    for (NSUInteger i = 0; i < 100; ++i)
    {
        // don't schedule new tasks until we have enough semaphore
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
                   {
                       [self recurWithSemaphore:sem];
                   });
    }
}

1 个答案:

答案 0 :(得分:0)

使用信号量控制对有限资源的访问时的典型模式是

  • 创建非零值的信号量;

  • 对于每项任务:

    • 开始时,&#34;等待&#34;对于信号量(从而消耗其中一个可用信号,或者如果没有信号,则等待一个);以及

    • 完成后,&#34;信号&#34;信号量(使其可用于其他任务)。

所以,让我们假设您想要启动1,000,000个任务,在任何给定时间只能同时执行4个任务,您可以执行以下操作:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(4);

dispatch_queue_t queue = ... // some concurrent queue, either global or your own

dispatch_async(queue, ^{
    for (long index = 0; index < 1000000; index++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            [self performSomeActionWithIndex:index completion:^{
                dispatch_semaphore_signal(semaphore);
            }];
        });
    }
});

显然,如果您要动态添加更多要执行的任务,可以将其从for循环更改为while循环,检查某些同步来源,但这个想法是相同的。

关键的观察结果是,我本身没有performSomeActionWithIndex递归创建任务(因为这样你就可以进入原始问题的僵局状态,因为它们可以和#39;开始新任务)。

现在,我不知道您的问题是否可以重构为这种模式,但如果可以的话,这可能是一种选择。


顺便说一句,为了完整起见,我指出控制并发度的典型解决方案是使用操作队列而不是调度队列,在这种情况下,您可以指定{{1} }。

正如您正确指出的那样,存在内存含义。在我的测试中,每个计划的操作至少占用500个字节(在实际场景中可能更多),因此如果您确实要安排超过5,000-10,000个任务,操作队列可能很快变得不切实际。正如您所建议的那样,未来的读者应该参考并发编程指南:并发和应用程序设计中的Performance Implications部分。

我知道这不是一个可行的方法,但我只是为了未来读者的利益而提及它。当需要控制并发度时,我通常会建议使用操作队列。如果您正在处理如此多的任务而无法合理地将它们安排在操作队列中,我只会跳到上面概述的方法。