同时填充数组

Dam*_*aux 4 memory memory-management grand-central-dispatch swift swift5

我在 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")
Run Code Online (Sandbox Code Playgroud)

当我使用 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
Run Code Online (Sandbox Code Playgroud)

我感觉这是编译器中的一些错误,因为当我在发布模式下运行代码时,它运行得很好。或者我在这里做错了什么?

Rob*_*ier 9

Array 中的指针绝对可以在您的脚下改变。它不是原始记忆。

数组不是线程安全的。数组是值类型,这意味着它们以线程安全的方式支持写时复制(因此您可以自由地将数组传递给另一个线程,如果它被复制到那里,那就可以了),但您不能在多个线程上改变相同的数组。数组不是 C 缓冲区。它没有承诺具有连续内存。它甚至根本没有承诺分配内存。Array 可以在内部选择将“我目前全为零”作为特殊状态存储,并且只为每个下标返回 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

“不安全”表示现在您的问题是确保所有访问都是合法的,并且您没有创建竞争条件。

注意.concurrentPerform这里的使用。正如@user3441734 提醒我们的那样,pixelsPtr不承诺一旦.withUnsafeMutablePointer完成即有效。.concurrentPerform保证在所有块完成之前不会返回,因此保证指针有效。

这也可以通过 DispatchGroup 完成,但.wait需要在withUnsafeMutableBufferPointer.

  • @ user3441734 我不太确定你在这里关于依赖当前实现的意思。`withUnsafeMutableBufferPointer` 旨在(并记录在案)直接访问数组的可变连续存储。这都是定义好的行为。你有什么实施问题?将大型阵列连接在一起可能非常昂贵;如果`size` 是例如 1 亿(考虑处理 21600x21600 BlueMarble 图像),我不确定你为什么说“性能损失最小”。 (2认同)