Corrupted memory with DispatchQueue.concurrentPerform

Viewed 764

I see a strange behavior with the following code (which runs on Playground).

import Foundation

let count = 100
var array = [[Int]](repeating:[Int](), count:count)

DispatchQueue.concurrentPerform(iterations: count) { (i) in
    array[i] = Array(i..<i+count)
}

// Evaluation
for (i,value) in array.enumerated() {
    if (value.count != count) {
        print(i, value.count)
    }
}

The result is different each time, and sometime crashes with memory corruption. It looks like a memory reallocation (of "array") is happening while another thread is accessing the memory.

Is this a bug (of iOS) or an expected behavior? Am I missing something?

3 Answers

This is expected behaviour. Swift arrays are not thread safe; That is, modifying a Swift array from multiple threads concurrently will cause corruption.

I realise that you are just experimenting, but even if arrays were thread safe, this would not be a good use of concurrentPerform and would probably perform worse than a simple for loop given the threading overhead.

Once you introduce an appropriate synchronisation method to guard the array update, such as dispatching that update onto a serial dispatch queue, it will definitely perform more slowly than a simple for loop

Here is the solution. Thank you for quick responses.

import Foundation

let count = 1000
var arrays = [[Int]](repeating:[Int](), count:count)
let dispatchGroup = DispatchGroup()
let lockQueue = DispatchQueue(label: "lockQueue")

DispatchQueue.concurrentPerform(iterations: count) { (i) in
    dispatchGroup.enter()
    let array = Array(i..<i+count) // The actual code is very complex
    lockQueue.async {
        arrays[i] = array
        dispatchGroup.leave()
    }
}

dispatchGroup.wait()

// Evaluation
for (i,value) in arrays.enumerated() {
    if (value.count != count) {
        print(i, value.count)
    }
}

In my case I had to generate 24k elements array with Float multiple times per second and it takes around 40ms for each on old iPhone 6.

Since each element of the array is only assigned once, I've decided to try raw array using pointers:

class UnsafeArray<T> {
    let count: Int
    let cArray: UnsafeMutablePointer<T>
    
    init(_ size: Int) {
        count = size
        cArray = UnsafeMutablePointer<T>.allocate(capacity: size)
    }
    
    subscript(index: Int) -> T {
        get { return cArray[index] }
        set { cArray[index] = newValue }
    }
    
    deinit {
        free(cArray)
    }
}

Then I used it like this:

let result = UnsafeArray<Float>(24000)
DispatchQueue.concurrentPerform(iterations: result.count, execute: { i in
    result[i] = someCalculation()
})

And it worked! Now it takes 9-16ms. Also, I have no memory leaks using this code.

Related