Заполнение массива одновременно - PullRequest
3 голосов
/ 16 марта 2019

Я наткнулся на проблему с параллелизмом и массивами в 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")

Когда я выполняю это в режиме отладки, используя версию XCode10.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

У меня такое ощущение, что это какая-то ошибка в компиляторе, потому что когда я запускаю код в режиме выпуска, он работает просто отлично.Или я тут что-то не так делаю?

1 Ответ

4 голосов
/ 16 марта 2019

Внутри массива есть указатели, которые могут совершенно измениться у вас под ногами.Это не необработанная память.

Массивы не являются поточно-ориентированными.Массивы являются типами значений, что означает, что они поддерживают копирование при записи потокобезопасным способом (так что вы можете свободно передавать массив в другой поток, и если он там скопирован, это нормально), но вы не можетемутировать один и тот же массив в нескольких потоках.Массив не является C-буфером.Не обещано иметь непрерывную память.Даже не обещали выделять память вообще.Массив может выбрать внутреннее хранение «Я в настоящее время все нули» в качестве особого состояния и просто вернуть 0 для каждого индекса.(Это не так, но разрешено.)

Для этой конкретной проблемы вы обычно используете методы vDSP, такие как vDSP_vramp, но я понимаю, что это всего лишь пример, а метода 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)
        }
    }
}

«Небезопасно» означает, что теперь ваша задача - убедиться, что все обращения разрешены, и что вы не создаете условия гонки.

Обратите внимание на использование .concurrentPerform здесь.Как напоминает нам @ user3441734, pixelsPtr не обещает быть действительным после завершения .withUnsafeMutablePointer..concurrentPerform гарантированно не вернется, пока все блоки не будут завершены, поэтому указатель гарантированно будет действительным.

Это можно сделать и с DispatchGroup, но .wait должно быть внутри withUnsafeMutableBufferPointer.

...