Search code examples
iosswiftgrand-central-dispatch

DispatchQueue barrier issue


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)

output of my code

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!


Solution

  • The way barrier works is by making sure the DispatchWorkItem (the block for append(:_)) is going wait until all other DispatchWorkItems 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 DispatchWorkItems 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 DispatchWorkItems 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 DispatchWorkItems 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.