I've just read a post by Basem Emara about creating a threadsafe array Type in Swift. While I glanced through the code example, I asked myself if there isn't a way to achieve this with quite less code.
Suppose I create this class:
// MARK: Class Declaration
class ThreadsafeArray<Element> {
// Private Variables
private var __array: [Element] = []
private var __arrayQueue: DispatchQueue = DispatchQueue(
label: "ThreadsafeArray.__concurrentArrayQueue",
attributes: .concurrent
)
}
// MARK: Interface
extension ThreadSafeArray {
// ReadWrite Variables
var threadsafe: [Element] {
get {
return self.__arrayQueue.sync {
return self.__array
}
}
set(newArray) {
self.__arrayQueue.async(flags: .barrier) {
self.__array = newArray
}
}
}
}
If, from now on, I only accessed the actual array through .threadsafe
, would this suffice to make the array threadsafe?
Also, could I implement it a struct instead of a class to get the mutating checks as well?
I am aware that the objects inside this array would not be threadsafe themselves through this but this is not the point, so let's assume I only put threadsafe stuff in there.
(Of course, to avoid the calls to .threadsafe
, I would make the shiny new class conform to ExpressibleByArrayLiteral
, Collection
and RangeReplaceableCollection
, so I can use it like a normal array.
Edit
Meanwhile, I've tried testing it in a playground and have come to believe that it doesn't suffice.
Playground code:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
// Testing //
// Thread-unsafe array
func unsafeArray() {
var array: [Int] = []
var iterations: Int = 1000
let start: TimeInterval = Date().timeIntervalSince1970
DispatchQueue.concurrentPerform(iterations: iterations) { index in
let last: Int = array.last ?? 0
array.append(last + 1)
DispatchQueue.global().sync {
iterations -= 1
// Final loop
guard iterations <= 0 else { return }
print(String(
format: "Unsafe loop took %.3f seconds, count: %d.",
Date().timeIntervalSince1970 - start, array.count
))
}
}
}
// Thread-safe array
func safeArray() {
let array: ThreadsafeArray<Int> = ThreadsafeArray<Int>()
var iterations: Int = 1000
let start: TimeInterval = Date().timeIntervalSince1970
DispatchQueue.concurrentPerform(iterations: iterations) { index in
let last: Int = array.threadsafe.last ?? 0
array.threadsafe.append(last + 1)
DispatchQueue.global().sync {
iterations -= 1
// Final loop
guard iterations <= 0 else { return }
print(String(
format: "Safe loop took %.3f seconds, count: %d.",
Date().timeIntervalSince1970 - start, array.threadsafe.count
))
}
}
}
unsafeArray()
safeArray()
Output:
Most of the time:
experiments(31117,0x7000038d0000) malloc: *** error for object 0x11f663d28: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Sometimes:
IndexError: Index out of range
Unfortunately also:
Unsafe loop took 1.916 seconds, count: 994.
Safe loop took 11.258 seconds, count: 515.
Doesn't seem to suffice (also, it's incredibly unperformant).