使用 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
,它包含处理图像的所有方法。
analyzeColors
是 Analyzer
class 中的一种方法,它执行大部分分析,return 是一个字符串,其中包含传入图像的主色
imageStrips
是 UIImage
的数组,它们构成了原始图像的部分
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" 总是更好。
就如何实现这一点而言,有两种基本技术:
如果你在 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 中进行了讨论。
另一种常见模式是创建一个调度组,使用该组将任务调度到并发队列,并指定一个 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 模式。但这对于这种情况可能已经足够了。
我正在尝试使用 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
,它包含处理图像的所有方法。
analyzeColors
是 Analyzer
class 中的一种方法,它执行大部分分析,return 是一个字符串,其中包含传入图像的主色
imageStrips
是 UIImage
的数组,它们构成了原始图像的部分
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" 总是更好。
就如何实现这一点而言,有两种基本技术:
如果你在 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 中进行了讨论。另一种常见模式是创建一个调度组,使用该组将任务调度到并发队列,并指定一个
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 模式。但这对于这种情况可能已经足够了。