使用 dispatch_async 在 Swift 中并发分析一个数组

Use dispatch_async to analyze an array concurrently in Swift

我正在尝试使用 GCD 的后台线程同时分析一张照片。这是我写的代码:

dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)) {
    for (var i = 0; i < 8; i++)
    {
        let color = self.photoAnalyzer.analyzeColors(imageStrips[i])
        colorList.append(color)
    }
}

为了澄清变量名称,这里是它们的描述:

photoAnalyzer 是我写的 class 的一个实例,名为 Analyzer,它包含处理图像的所有方法。

analyzeColorsAnalyzer class 中的一种方法,它执行大部分分析,return 是一个字符串,其中包含传入图像的主色

imageStripsUIImage 的数组,它们构成了原始图像的部分

colorList 是一个字符串数组,用于存储图像每个部分的 analyzeColor 方法的 return 值。

以上代码按顺序运行,因为 for 循环一次只访问 imageList 中的一张图像。我想做的是同时分析 imageStrips 中的每个图像,但我不知道该怎么做。

如有任何建议,我们将不胜感激。如果您想查看所有代码以进一步帮助我,我可以 post a GitHub link 到它。

编辑 这是我更新的代码,可以同时处理 8 个处理器。

dispatch_apply(8, imageQueue) { numStrips -> Void in
    let color = self.photoAnalyzer.analyzeColors(imageStrips[numStrips])
    colorList.append(color)
}

但是,如果我尝试使用超过 8 个,代码实际运行速度会比顺序运行慢。

dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)) {
    for (var i = 0; i < 8; i++)
    {
 dispatch_async(dispatch_get_main_queue(), ^(){
    //Add method, task you want perform on mainQueue
    //Control UIView, IBOutlet all here
        let color = self.photoAnalyzer.analyzeColors(imageStrips[i])
        colorList.append(color)
    });
    }
}

有几种方法可以做到这一点,但在我们开始之前有一些观察:

  • 为了尽量提高性能,如果您进行任何并发处理,请注意,您无法保证它们的完成顺序。因此,如果它们出现的顺序很重要,那么简单的 colorList.append(color) 模式将不起作用。您可以预填充 colorList 然后让每次迭代简单地执行 colorList[i] = color 或者您可以使用字典。 (很明显,如果顺序不重要,那么这个就不重要了。)

  • 因为这些迭代将 运行 同时进行,您需要同步 colorList 的更新。因此,在后台队列上同时执行昂贵的 analyzeColors,但使用串行队列更新 colorList,以确保您没有多个更新相互跨越。

  • 做并发处理时,有递减点returns。例如,将一项复杂的任务分解为 2-4 个并发循环可能会产生一些性能优势,但如果开始过多地增加并发线程数,您会发现这些线程的开销开始对性能产生不利影响.所以用不同的并发度来做基准测试,不要假设 "more threads" 总是更好。

就如何实现这一点而言,有两种基本技术:

  1. 如果你在 Concurrency Programming Guide: Dispatch Queues 指南中看到 Performing Loop Iterations Concurrently,他们谈论 dispatch_apply,这是设计的正是为了这个目的,运行 for 并发循环。

    colorList = [Int](count: 8, repeatedValue: 0)  // I don't know what type this `colorList` array is, so initialize this with whatever type makes sense for your app
    
    let queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
    
    let qos_attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0)
    let syncQueue = dispatch_queue_create("com.domain.app.sync", qos_attr)
    
    dispatch_apply(8, queue) { iteration in
        let color = self.photoAnalyzer.analyzeColors(imageStrips[iteration])
        dispatch_sync(syncQueue) {
            colorList[iteration] = color
            return
        }
    }
    
    // you can use `colorList` here
    

    请注意,虽然这些迭代 运行 并发,但整个 dispatch_apply 循环 运行 与您启动它的队列同步。这意味着你不会想从主线程调用上面的代码(我们永远不想阻塞主线程)。因此可能希望将整个事情分派到某个后台队列。

    顺便说一句,dispatch_apply 在 WWDC 2011 视频 Blocks and Grand Central Dispatch in Practice 中进行了讨论。

  2. 另一种常见模式是创建一个调度组,使用该组将任务调度到并发队列,并指定一个 dispatch_group_notify 以指定完成后要执行的操作。

    colorList = [Int](count: 8, repeatedValue: 0)  // I don't know what type this `colorList` array is, so initialize this with whatever type makes sense for your app
    
    let group = dispatch_group_create()
    let queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
    
    let qos_attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0)
    let syncQueue = dispatch_queue_create("com.domain.app.sync", qos_attr)
    
    for i in 0 ..< 8 {
        dispatch_group_async(group, queue) {
            let color = self.photoAnalyzer.analyzeColors(imageStrips[i])
            dispatch_sync(syncQueue) {
                colorList[i] = color
                return
            }
        }
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue()) {
        // use `colorList` here
    }
    
    // but not here (because the above code is running asynchronously)
    

    这种方法避免了完全阻塞主线程,但您必须注意不要添加太多并发分派的任务(因为工作线程是非常有限的资源)。

在这两个示例中,我都创建了一个专用串行队列来将更新同步到 colorList。这可能有点矫枉过正。如果您没有阻塞主队列(无论如何都不应该这样做),您可以将此同步代码分派到主队列(这是一个串行队列)。但是为此目的有一个专用的串行队列可能更精确。如果这是我要不断从多个线程与之交互的东西,我会使用 reader-writer 模式。但这对于这种情况可能已经足够了。