并发填充数组
Filling an array concurrently
我在 Swift 5 中遇到了并发和数组问题。为了重现这个问题,我将我的代码简化为以下片段:
import Dispatch
let group = DispatchGroup()
let queue = DispatchQueue(
label: "Concurrent threads",
qos: .userInitiated,
attributes: .concurrent
)
let threadCount = 4
let size = 1_000
var pixels = [SIMD3<Float>](
repeating: .init(repeating: 0),
count: threadCount*size
)
for thread in 0..<threadCount {
queue.async(group: group) {
for number in thread*size ..< (thread+1)*size {
let floating = Float(number)
pixels[number] = SIMD3<Float>(floating, floating, floating)
}
}
}
print("waiting")
group.wait()
print("Finished")
当我使用 Xcode 版本 10.2 beta 4 (10P107d) 在调试模式下执行此操作时,它总是崩溃并出现如下错误:
Multithread(15095,0x700008d63000) malloc: *** error for object 0x104812200: pointer being freed was not allocated
Multithread(15095,0x700008d63000) malloc: *** set a breakpoint in malloc_error_break to debug
我觉得这是编译器中的一些错误,因为当我 运行 代码处于发布模式时,它 运行 就很好。还是我做错了什么?
数组里面有指针,绝对可以在你脚下改变。它不是原始内存。
数组不是线程安全的。数组是值类型,这意味着它们以线程安全的方式支持写时复制(所以你可以自由地将一个数组传递给另一个线程,如果它被复制到那里,那是可以的),但你不能在多个线程上改变同一个数组。数组不是 C 缓冲区。它没有承诺具有连续的内存。它甚至根本没有承诺分配内存。数组可以选择在内部将 "I'm currently all zeros" 存储为特殊状态,并且每个下标仅存储 return 0。 (它没有,但它是允许的。)
对于这个特定问题,您通常会使用 vDSP_vramp 等 vDSP 方法,但我知道这只是一个示例,可能没有解决该问题的 vDSP 方法。不过,通常情况下,我仍然会专注于 Accelerate/SIMD 方法,而不是调度到队列。
但是如果你要调度到队列,你需要一个 UnsafeMutableBuffer 来控制内存(并确保内存甚至存在):
pixels.withUnsafeMutableBufferPointer { pixelsPtr in
DispatchQueue.concurrentPerform(iterations: threadCount) { thread in
for number in thread*size ..< (thread+1)*size {
let floating = Float(number)
pixelsPtr[number] = SIMD3(floating, floating, floating)
}
}
}
"Unsafe" 表示现在您的问题是确保所有访问都是合法的并且您没有创建竞争条件。
注意这里使用了.concurrentPerform
。正如@user3441734 提醒我们的那样,一旦 .withUnsafeMutablePointer
完成,pixelsPtr
并不承诺有效。 .concurrentPerform
保证在所有块都完成之前不会return,所以指针保证有效。
这也可以用 DispatchGroup 完成,但是 .wait
需要在 withUnsafeMutableBufferPointer
.
内部
我在 Swift 5 中遇到了并发和数组问题。为了重现这个问题,我将我的代码简化为以下片段:
import Dispatch
let group = DispatchGroup()
let queue = DispatchQueue(
label: "Concurrent threads",
qos: .userInitiated,
attributes: .concurrent
)
let threadCount = 4
let size = 1_000
var pixels = [SIMD3<Float>](
repeating: .init(repeating: 0),
count: threadCount*size
)
for thread in 0..<threadCount {
queue.async(group: group) {
for number in thread*size ..< (thread+1)*size {
let floating = Float(number)
pixels[number] = SIMD3<Float>(floating, floating, floating)
}
}
}
print("waiting")
group.wait()
print("Finished")
当我使用 Xcode 版本 10.2 beta 4 (10P107d) 在调试模式下执行此操作时,它总是崩溃并出现如下错误:
Multithread(15095,0x700008d63000) malloc: *** error for object 0x104812200: pointer being freed was not allocated
Multithread(15095,0x700008d63000) malloc: *** set a breakpoint in malloc_error_break to debug
我觉得这是编译器中的一些错误,因为当我 运行 代码处于发布模式时,它 运行 就很好。还是我做错了什么?
数组里面有指针,绝对可以在你脚下改变。它不是原始内存。
数组不是线程安全的。数组是值类型,这意味着它们以线程安全的方式支持写时复制(所以你可以自由地将一个数组传递给另一个线程,如果它被复制到那里,那是可以的),但你不能在多个线程上改变同一个数组。数组不是 C 缓冲区。它没有承诺具有连续的内存。它甚至根本没有承诺分配内存。数组可以选择在内部将 "I'm currently all zeros" 存储为特殊状态,并且每个下标仅存储 return 0。 (它没有,但它是允许的。)
对于这个特定问题,您通常会使用 vDSP_vramp 等 vDSP 方法,但我知道这只是一个示例,可能没有解决该问题的 vDSP 方法。不过,通常情况下,我仍然会专注于 Accelerate/SIMD 方法,而不是调度到队列。
但是如果你要调度到队列,你需要一个 UnsafeMutableBuffer 来控制内存(并确保内存甚至存在):
pixels.withUnsafeMutableBufferPointer { pixelsPtr in
DispatchQueue.concurrentPerform(iterations: threadCount) { thread in
for number in thread*size ..< (thread+1)*size {
let floating = Float(number)
pixelsPtr[number] = SIMD3(floating, floating, floating)
}
}
}
"Unsafe" 表示现在您的问题是确保所有访问都是合法的并且您没有创建竞争条件。
注意这里使用了.concurrentPerform
。正如@user3441734 提醒我们的那样,一旦 .withUnsafeMutablePointer
完成,pixelsPtr
并不承诺有效。 .concurrentPerform
保证在所有块都完成之前不会return,所以指针保证有效。
这也可以用 DispatchGroup 完成,但是 .wait
需要在 withUnsafeMutableBufferPointer
.