当前位置: 首页 > 知识库问答 >
问题:

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

曾光远
2023-03-14

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

然而,我发现当运行线程达到最大数量(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个答案

曹涵润
2023-03-14

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

>

  • 创建非零值信号量;

    对于每项任务:

    >

    完成后,“信号”信号量(使其可用于另一项任务)。

    所以,假设你想启动1000000个任务,在任何给定时间只有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循环更改为循环,检查一些同步的源,但想法是相同的。

    然而,关键的观察结果是,我并没有让performSomeActionWithIndex本身递归地创建任务本身(因为这样你就进入了原始问题的死锁状态,任务停止了,因为它们无法启动新任务)。

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

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

    正如你正确指出的,这会影响记忆。在我的测试中,每个计划的操作至少占用500字节(在现实世界的场景中可能会更多),因此,如果要计划的任务确实超过5000-10000个,那么操作队列可能很快就会变得不切实际。正如您所建议的,未来的读者应该参考《并发编程指南:并发和应用程序设计》中的性能含义部分。

    我知道在你的情况下,这不是一个可行的方法,但我提到它只是为了未来读者的利益。我通常建议在需要控制并发程度时使用操作队列。如果你处理的任务太多,以至于不能合理地将它们安排在一个操作队列中,我只会跳到上面概述的方法。

  •  类似资料:
    • 我有以下四个测试,最后一个测试在运行时挂起。为什么会发生这种情况: 我使用这个扩展方法的restSharp RestClient: 为什么最后一个测试挂起?

    • 我在网上搜索了很多关于vs await async,但是在这个特定的使用场景中,我并不真正理解其中的区别。我相信情况很简单。 vs. 其中,是一个异步方法,其中包含一些异步调用,例如使用wait调用db。 问题: 在这种情况下,两者之间有什么区别吗?任何帮助或意见,谢谢!

    • 使用asyn/wait vs wait有什么区别task.run() 等待任务。运行示例- 异步等待示例-

    • 问题内容: 我正在与a 并行处理许多项目。尽管线程本身可以正常工作,但是由于线程中发生的动作,有时我们会遇到其他资源限制,这使我们想减少池中的线程数。 我想知道在线程实际工作时是否有办法降低线程数。我知道您可以调用和/或,但是它们仅在线程变为空闲时才调整池的大小,但是直到队列中没有任何任务等待时它们才变为空闲。 问题答案: 据我所知,这不可能是一种很好的清洁方式。 您可以实现beforeExecu

    • 在C#中,我有以下两个简单的例子: 第一个示例创建一个打印“开始”的任务,等待5秒钟打印“完成”,然后结束任务。我等待任务完成,然后打印“全部完成”。当我运行测试时,它会按预期运行。 第二个测试应该具有相同的行为,只是由于使用了async和Wait,任务内部的等待应该是非阻塞的。但是这个测试只打印“开始”,然后立即打印“全部完成”和“完成”,永远不会打印。 我不知道我为什么会有这样的行为:S非常感

    • 问题内容: 等待所有任务完成的最简单方法是什么?我的任务主要是计算,所以我只想运行大量的作业-每个内核上一个。现在,我的设置如下所示: 实现可运行。这似乎是正确执行的任务,但代码崩溃上用。这很奇怪,因为我玩了一些玩具示例,而且看起来很奏效。 包含数以万计的元素。我应该使用其他方法吗?我正在寻找尽可能简单的东西 问题答案: 最简单的方法是使用单行代码执行所需的操作。用你的话来说,你需要修改或包装以实