0

I have the following code, which to my understanding should provide thread safe read and write on _predictions. My isolationQueue is a concurrent queue and needs to stay as such. I call multiple independent async operations on this queue to calculate predictions for various images. The only shared item between the various calls is the when prediction is set.

  var isolationQueue = DispatchQueue.global(qos: .default)
  var _predictions: [Int:[Prediction]] = [:]

  var predictions:[Int: [Prediction]] {
    get {
      var result: [Int: [Prediction]]!
      isolationQueue.sync {
        result = _predictions
      }
      return result
    }
    set(value) {
      isolationQueue.sync {
        self._predictions = value
      }
    }
  }

However for some reason Thread Sanitizer seems to detect a racing condition between the getter and the setter.

enter image description here enter image description here

Am I missing something?

Reza Shirazian
  • 2,303
  • 1
  • 22
  • 30

2 Answers2

1

Global dispatch queues are concurrent queues, so they cannot be used to protect against concurrent access of a resource.

You should define your own serial dispatch queue for that purpose:

var isolationQueue = DispatchQueue(label: "my.queue.identifier") 
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • using `sync` does not protect against other threads accessing the resources while its being executed in a concurrent queue? Is there anyway to ensure serial access on a concurrent queue? – Reza Shirazian Oct 02 '17 at 03:56
  • @RezaShirazian: `sync` means that the calling thread waits for completion, but blocks dispatched from different threads can execute simultaneously. Here is a good overview of the 4 possible combinations: https://stackoverflow.com/questions/19179358/concurrent-vs-serial-queues-in-gcd. – Martin R Oct 02 '17 at 03:59
  • what do I do with the `sync` then? if my `isolationQueue` is serial `sync` doesn't seem to make a lot of sense in this case. – Reza Shirazian Oct 02 '17 at 04:17
  • @RezaShirazian: `isolationQueue.async { ... }` would return immediately, without waiting for the item to be executed. sync/async and serial/concurrent are independent parameters. Have a look at https://stackoverflow.com/a/19179791/1187415 and try it!. – (You can use `async` on the setter, but not on the getter method.) – Martin R Oct 02 '17 at 04:22
  • 1
    Thanks Martin. I think I figured it out. For one my queue needs to remain concurrent as I need to run multiple independent async operations for calculating predictions. Setting my setter to be `async(flags: .barrier)` ensures that when I do set the prediction dictionary no other operation on this queue modifies it. – Reza Shirazian Oct 02 '17 at 23:41
0

For your case, using DispatchSemaphore() is simpler and has less overhead. The code looks like

var isolationSem = DispatchSemaphore(value: 1)
var _predictions: [Int:[Prediction]] = [:]

var predictions:[Int: [Prediction]] {
  get {
    var result: [Int: [Prediction]]!
    isolationSem.wait()
    result = _predictions
    isolationSem.signal()
    return result
  }
  set(value) {
    isolationSem.wait()
    self._predictions = value
    isolationSem.signal()
  }
}

See this for the case where DispatchSemaphore is not suitable.

beshio
  • 794
  • 2
  • 7
  • 17