Reputation: 1145
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
Reputation: 233
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
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