如何在不达到全局GCD限制的情况下并行化许多(100+)任务?

时间:2015-06-19 08:19:07

标签: ios objective-c multithreading asynchronous grand-central-dispatch

问题

当在后台延迟加载100个以上图标的列表时,我达到了GCD线程限制(64个线程),这导致我的应用程序在主线程上冻结了semaphore_wait_trap。我想重构我的线程代码以防止这种情况发生,同时仍然加载图标异步以防止UI阻塞。

上下文

我的应用加载了一个带有SVG图标的屏幕。金额平均为10-200。通过使用本地SVG图像或远程SVG图像(如果它具有自定义图标)绘制图标,然后对它们进行后处理以获得最终图像结果。

因为这需要一些时间,并且它们对用户来说并不重要,所以我想在后台加载和后处理它们,因此它们会随着时间推移而流行。对于每个图标,我使用以下内容:

dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
    //code to be executed in the background
    SVGKImage *iconImage = [Settings getIconImage:location];
    dispatch_async(dispatch_get_main_queue(), ^{
        //code to be executed on the main thread when background task is finished
        if (iconImage) {
            [iconImgView setImage:iconImage.UIImage];
        }
    });
});

getIconImage方法处理基本SVG的初始加载,如果是本地,则读取它与[NSInputStream inputStreamWithFileAtPath:path]同步,如果它应该远程加载,则[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&errorWithNSData]。这一切都是同步的。

然后有一些重新定义SVG的后处理,在它返回并放入主线程的UIImageView之前。

问题:

有没有办法构建我的代码以允许并行化后台加载但是由于线程太多而阻止死锁?

解决方案编辑:

_iconOperationQueue = [[NSOperationQueue alloc]init];
_iconOperationQueue.maxConcurrentOperationCount = 8;    

// Code will be executed on the background
[_iconOperationQueue addOperationWithBlock:^{
    // I/O code
    SVGKImage *baseIcon = [Settings getIconBaseSVG:location];

    // CPU-only code
    SVGKImage *iconImage = [Settings getIconImage:location withBaseSVG:baseIcon];
    UIImage *svgImage = iconImage.UIImage; // Converting SVGKImage to UIImage is expensive, so don't do this on the main thread
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // Code to be executed on the main thread when background task is finished
        if (svgImage) {
            [iconImgView setImage:svgImage];
        }
    }];
}];

2 个答案:

答案 0 :(得分:3)

不要直接将GCD与并发队列一起使用,而是使用NSOperationQueue。将其maxConcurrentOperationCount设置为合理的值,例如4或8。

如果可以,您还应该将I / O与纯计算分开。对I / O使用宽度受限的操作队列。纯计算可以使用不受限制的操作队列或纯GCD。

原因是I / O块。 GCD检测到系统处于空闲状态并且旋转另一个工作线程并从队列中启动另一个任务。这也阻止了I / O,所以它会更多地执行,直到它达到其极限。然后,I / O开始完成并且任务解除阻塞。现在你已经超额认购了系统资源(即CPU),因为飞行中的任务比核心多,突然他们实际上在使用CPU而不是被I / O阻塞。

纯计算任务不会引发此问题,因为GCD发现系统实际上很忙,并且在较早的任务完成之前不会将更多任务出列。

答案 1 :(得分:3)

你可以使用类似这样的信号量来保持GCD在后台运行整个操作,否则等待信号量将阻止UI:

dispatch_semaphore_t throttleSemaphore = dispatch_semaphore_create(8);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for /* Loop through your images */ {
    dispatch_semaphore_wait(throttleSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(concurrentQueue, ^{
        //code to be executed in the background
        SVGKImage *iconImage = [Settings getIconImage:location];
        dispatch_async(dispatch_get_main_queue(), ^{
            //code to be executed on the main thread when background task is finished
            if (iconImage) {
                [iconImgView setImage:iconImage.UIImage];
            }
            dispatch_semaphore_signal(throttleSemaphore);
        });
    });
}