我有一个大型数组,我希望通过将它的片段交给几个异步任务来处理。作为概念证明,我编写了以下代码:
class TestParallelArrayProcessing {
let array: [Int]
var summary: [Int]
init() {
array = Array<Int>(count: 500000, repeatedValue: 0)
for i in 0 ..< 500000 {
array[i] = Int(arc4random_uniform(10))
}
summary = Array<Int>(count: 10, repeatedValue: 0)
}
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
for x in base ..< base + 50000 {
self.summary[i] += self.array[x]
}
})
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
}
在init()
之后,array
将使用0到9之间的随机整数进行初始化。
calcSummary
函数调度10个任务,这些任务从array
中取出50000个项目的不相交块,并使用summary
中的相应插槽作为累加器将其相加。
此程序在self.summary[i] += self.array[x]
行崩溃。错误是:
EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP).
我可以在调试器中看到它在崩溃之前已经设法迭代了几次,并且在崩溃时变量的值都在正确的范围内。
我已经读过在尝试访问已经发布的对象时可能会发生EXC_I386_INVOP
。我想知道这是否与Swift制作数组的副本有关,如果它被修改,如果是这样,如何避免它。
答案 0 :(得分:5)
使用Array
类型的withUnsafeMutableBufferPointer<R>(body: (inout UnsafeMutableBufferPointer<T>) -> R) -> R
方法,这与@ Eduardo的回答略有不同。 That method's documentation states:
调用
body(p)
,其中p
是指向Array
可变连续存储的指针。如果不存在此类存储,则首先创建它。通常,优化器可以消除数组算法中的边界和唯一性检查,但是当失败时,在
body
的参数上调用相同的算法可以让您以安全速度进行交易。
第二段似乎正是这里发生的事情,所以使用这种方法在Swift中可能更“惯用”,无论这意味着什么:
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
self.summary.withUnsafeMutableBufferPointer {
summaryMem -> Void in
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
for x in base ..< base + 50000 {
summaryMem[i] += self.array[x]
}
})
}
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
答案 1 :(得分:3)
当你使用+=
运算符时,LHS是一个inout
参数 - 我认为你正在获得竞争条件,正如你在更新中提到的那样,Swift在数组中移动以进行优化。通过将局部变量中的块相加,然后简单地分配到summary
中的正确索引,我能够使它工作:
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
var sum = 0
for x in base ..< base + 50000 {
sum += self.array[x]
}
self.summary[i] = sum
})
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
答案 2 :(得分:2)
我认为Nate是对的:summary
变量存在竞争条件。为了解决这个问题,我直接使用了summary
的内存:
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
let summaryMem = UnsafeMutableBufferPointer<Int>(start: &summary, count: 10)
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
for x in base ..< base + 50000 {
summaryMem[i] += self.array[x]
}
})
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
这是有效的(到目前为止)。
修改强> Mike S在下面的评论中有一个非常好的观点。我还找到了this blog post,它解释了这个问题。
答案 3 :(得分:2)
你也可以使用concurrentPerform(iterations: Int, execute work: (Int) -> Swift.Void)
(自Swift 3起)。
它有一个更简单的语法:
DispatchQueue.concurrentPerform(iterations: iterations) {i in
performOperation(i)
}
并等待所有线程在返回之前完成。
答案 4 :(得分:0)
任何同时分配数组第i个元素的解决方案都有竞争条件的风险(Swift的数组不是线程安全的)。另一方面,在更新之前分派到同一队列(在这种情况下为main)解决了该问题,但导致整体性能降低。我看到采用这两种方法之一的唯一原因是,如果数组(摘要)无法等待所有并发操作完成。
否则,对本地副本执行并发操作,并在完成时将其分配给摘要。没有比赛条件,没有性能影响:
快捷键4
func calcSummary(of array: [Int]) -> [Int] {
var summary = Array<Int>.init(repeating: 0, count: array.count)
let iterations = 10 // number of parallel operations
DispatchQueue.concurrentPerform(iterations: iterations) { index in
let start = index * array.count / iterations
let end = (index + 1) * array.count / iterations
for i in start..<end {
// Do stuff to get the i'th element
summary[i] = Int.random(in: 0..<array.count)
}
}
return summary
}
我已经回答了类似的问题here,该问题只是在对另一个数组进行计算之后简单地初始化了一个数组