Me estoy topando con un problema con la concurrencia y las matrices en Swift 5. Para reproducir el problema, simplifiqué mi código al siguiente fragmento:
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")Cuando ejecuto esto en modo de depuración usando Xcode Versión 10.2 beta 4 (10P107d) siempre falla con un error como:
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 debugTengo la sensación de que esto es un error en el compilador porque cuando ejecuto el código en modo de lanzamiento, funciona bien. ¿O estoy haciendo algo mal aquí?
Hay punteros dentro de una matriz que absolutamente pueden cambiar bajo sus pies. No es memoria cruda.
Las matrices no son seguras para subprocesos. Las matrices son tipos de valor, lo que significa que admiten la copia en escritura de una manera segura para subprocesos (por lo que puede pasar libremente una matriz a otro subproceso, y si se copia allí, está bien), pero no puede mutar la misma matriz en varios subprocesos. Una matriz no es un búfer C. No se promete tener memoria contigua. Ni siquiera se promete asignar memoria en absoluto. Array podría elegir internamente almacenar "Actualmente soy todo ceros" como un estado especial y simplemente devolver 0 para cada subíndice. (No lo hace, pero está permitido).
Para este problema específico, normalmente usaría métodos vDSP como vDSP_vramp, pero entiendo que esto es solo un ejemplo, y es posible que no haya un método vDSP que resuelva el problema. Sin embargo, por lo general, todavía me enfoco en los métodos Accelerate/SIMD en lugar de despachar a las colas.
Pero si va a enviar a colas, necesitará un UnsafeMutableBuffer para tomar el control de la memoria (y asegurarse de que la memoria exista):
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) } } }El "Inseguro" indica que ahora es su problema asegurarse de que todos los accesos sean legales y que no esté creando condiciones de carrera.
Tenga en cuenta el uso de .concurrentPerform aquí. Como @user3441734 nos recuerda, no se promete que pixelsPtr sea válido una vez que se completa .withUnsafeMutablePointer . Se garantiza que .concurrentPerform no regresará hasta que todos los bloques estén completos, por lo que se garantiza que el puntero sea válido.
Esto también podría hacerse con DispatchGroup, pero .wait tendría que estar dentro de withUnsafeMutableBufferPointer .