Search code examples
swiftmultithreadingasynchronousgrand-central-dispatchdispatch-queue

Why does DispatchQueue.sync cause Data race?


Based on my printed output in the console window, the work in que2 was only executed after the que1 fully finished its work, so my question is why did I get the Data race warning even though the first block of work in que1 was completely synchronous?

Data race in closure #2 () -> () in BlankSwift at BlankSwift.porsche : BlankSwift.Car

struct Car {
  var name: String
}

let que1 = DispatchQueue(label: "que1", qos: .background)
let que2 = DispatchQueue(label: "que2", qos: .userInteractive)

var porsche = Car(name: "Porsche")

for i in 0...100 {
  que1.sync {
    porsche.name = "porsche1"
    print(porsche.name)
    porsche.name = "Porsche11"
    print(porsche.name)
    if i == 100 { print("returned ")}
  }

  que2.async {
     porsche.name = "porsche2"
     print(porsche.name)
     porsche.name = "Porsche22"
     print(porsche.name)
  }
}

Solution

  • While que1.sync is indeed called synchronously, que2.async is asynchronous on a different queue, so it schedules its closure and immediately returns, at which point you go to the next iteration of the loop.

    There is some latency before the que2 closure begins executing. So for example, the closure for que2.async that was scheduled for iteration 0, is likely to start executing while que1.sync is executing for some later iteration, let's say iteration 10.

    Not only that, que2 may well have multiple tasks queued up before the first one begins. It's a serial queue, because you didn't specify the .concurrent attribute, so you don't have to worry about que2 tasks racing on another que2 closure access of porsche.name , but they definitely can race on que1 closure accesses.

    As for output ordering, ultimately the output will go to FileHandle.standardOutput to which the OS has a buffer attached, and you don't know what kind of synchronization scheme the OS uses to order writes to that buffer. It may well use it's own call to DispatchQueue.async to ensure that I/O is done in a sensible way, much the way UI updates on AppKit/UIKit have to be done on the main thread.