Search code examples
iosgrand-central-dispatch

Understanding deadlock on nested DispatchQueue calls


I have 2 similar cases, first one

let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")

queue1.sync {
   print(1, Thread.current)

   queue2.sync {
      print(2, Thread.current)
   }

   print(3, Thread.current)
}

// result
1 <NSThread: 0x600001700140>{number = 1, name = main}
2 <NSThread: 0x600001700140>{number = 1, name = main}
3 <NSThread: 0x600001700140>{number = 1, name = main}

and second one

queue1.sync {
   print(1, Thread.current)

   DispatchQueue.main.sync {
      print(2, Thread.current)
   }

   print(3, Thread.current)
}

// result
1 <NSThread: 0x60000170c8c0>{number = 1, name = main}
deadlock

As we can see, first case uses 2 serial queues, all blocks called on the main thread without deadlock. Second case uses 2 serial queues (one if them is main queue) and also called on main thread, but now this causes deadlock. So what's the difference?


Solution

  • When you submit a task with .sync(), the current thread (no matter which one) has to wait until the task is completed, because that's the point of .sync(). Given that the thread is blocked from continuing, once the queue to which it was submitted is allowed to run the task, it will actually run right on the thread which called .sync(). There's no need for GCD to recruit a thread from its thread pool because there's a known thread available. (This is not guaranteed, as such. It's an optimization. But it explains why in your first snippet all of the tasks run on the main thread.)

    In your second snippet, though, my phrase "once the queue […] is allowed to run the task" comes into effect. The main queue is already running something: the call to queue1.sync(). The main queue is not allowed to run another task until that one completes, because it's serial. (Note, the main queue and the main thread, while intimately related, are not the same thing.) Therefore, the task that's running on the main queue is blocked waiting for the task that's running on the main queue to complete. Hence, deadlock.