trying to make thread safe array but it works not as I expected
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
public class SafeArray<Element> {
private var array = [Element]()
private let queue = DispatchQueue(label: "queueBarrier", attributes: .concurrent)
public func append(element: Element) {
queue.async(flags: .barrier) {
self.array.append(element)
}
}
public var elements: [Element] {
var result = [Element]()
queue.sync {
result = self.array
}
return result
}
public var last: Element? {
var result: Element?
queue.sync {
result = self.array.last
}
return result
}
}
var safeArray = SafeArray<Int>()
var array = Array<Int>()
DispatchQueue.concurrentPerform(iterations: 10) { (int) in
let last = array.last ?? 0
array.append(last + 1)
print("array = [..\(last)]")
}
print(array)
DispatchQueue.concurrentPerform(iterations: 10) { (int) in
let last = safeArray.last ?? 0
safeArray.append(element: last + 1)
print("safeArray = [..\(last)]")
}
print(safeArray.elements)
I expected that array should have some mess but safeArray should have numbers from 0 to 9.
I understand that array have 3 values but safeArray has 10 values as expected. But why this values not from 0 to 9?
Thank you!
The way barrier
works is by making sure the DispatchWorkItem
(the block for append(:_)
) is going wait until all other DispatchWorkItem
s are done before executing its perform
(the code inside the block).
Hence, a barrier
.
If you look closely, you got one (DispatchWorkItem
) inside your last
call.
Since you're calling last
the first thing you do concurrently in DispatchQueue.concurrentPerform
, you'll have a stack of DispatchWorkItem
s waiting in the queue.
This means all your append(_:)
calls will be waiting since they're flagged as barrier
and your last
calls will all execute first, thus getting a lot of zeroes until all the DispatchWorkItem
s for last
are done before squeezing in two appends(_:)
The way barrier
works within a concurrent queue, is that it will actually wait until all pending DispatchWorkItem
s are done, before starting, and nothing else will start concurrently alongside it until it's done.
It sorta "disables" the concurrent nature of the queue temporarily, unless I'm mistaken.
I'm generally hesitant to introduce locks or semaphore as suggested by others here and they can cause more issues unless you first understand how GCD works.
It looks like you're trying to solve two things, first having an append(_:)
that works concurrently and mutating an array in an parallel operation that depends on its current state. Try to break down what you're trying to solve first so someone can give you a better answer.