Andre
Andre

Reputation: 1145

Concurrency without semaphore in Swift

I'm trying to fix a concurrency race condition in my code, and I've been thinking about queues. Anybody can help me to understand why the following code doesn't work on iOS?

class Concurrency {
    private var privateStuff: Int  =  0
    var accessQueue = DispatchQueue(label: "read", qos: DispatchQoS.background, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
    var writequeue = DispatchQueue(label: "write")
    var stuff: Int {
        get {
            var result: Int!
            accessQueue.sync {
                result = privateStuff
            }
            return result
        }
        set {
            accessQueue.suspend()
            writequeue.sync {
                    privateStuff = newValue
            }
            accessQueue.resume()
        }
    }
}

I still have a race condition, so Xcode says, if I run a high number of reader/writer on this.

Am I missing something? Thanks

Upvotes: 0

Views: 664

Answers (2)

I'm using following class to deal with race conditions:

public class Protected<Value> {

    private var queue = DispatchQueue(label: "me.gk.Lightning.Protected", attributes: .concurrent)
    private var _value: Value

    public var value: Value {
        get {
            return queue.sync { _value }
        }
        set {
            queue.async(flags: .barrier) { [weak self] in
                self?._value = newValue
            }
        }
    }

    public init(_ value: Value) {
        _value = value
    }

    public func read(_ block: (Value) -> Void) {
        queue.sync {
            block(_value)
        }
    }

    public func write(_ block: @escaping (inout Value) -> Void) {
        queue.async(flags: .barrier) { [weak self] in
            guard let strongSelf = self else { return }
            block(&strongSelf._value)
        }
    }
}

Reference: https://github.com/gokselkoksal/Lightning

Usage:

var list = Protected(["item1"])

// Get value:
let items = list.value

// Set value:
list.value = ["item1", "item2"]

// Read block:
list.read { items in
    print(items)
}

// Write block:
list.write { items in
    items.append(...)
}

Upvotes: 2

Giuseppe Lanza
Giuseppe Lanza

Reputation: 3699

looks like this solution fits your problem:

https://medium.com/@oyalhi/dispatch-barriers-in-swift-3-6c4a295215d6

The data race condition is given by the fact that you are using two different queues. There is nothing that does not block other threads to suspend accessQueue in the setter while another thread is asking for the getter of the property.

Like described in the article in the link there are few ways to solve this reader-writer problem.

My favourite is with barriers.

Also, I would be defensive with those queues setting them as private let instead of vars to avoid some brilliant colleague to pass mainQueue as accessQueue causing a thread lock on the main thread.

So the whole thing would look like that:

class Concurrency {
    private var privateStuff: String  =  🖕
    private let queue = DispatchQueue(label: "read-write", qos: DispatchQoS.background, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
    var stuff: String {
        get {
            var result: String!
            queue.sync {
                result = privateStuff
            }
            return result
        }
        set {
            queue.async(flags: .barrier) {
                    privateStuff = newValue
            }
        }
    }
}

Upvotes: 2

Related Questions