I have found this to produce a deadlock, but I can't figure out why. Basically, I have a queue inside a class and every time the state of the class is supposed to be changed, I run that task inside the queue as a synchronous task:
private var serialQueue = DispatchQueue(label: "my_mutex_queue")
func changeState() {
serialQueue.sync {
// perform change
}
}
There are certain changes of state that require a call to a delegate. In this case, the task cannot be called synchronously, because it will cause a deadlock. However, dispatching it asynchronously also results in a deadlock (we are still inside the synchronous task "changeState" in the queue "my_mutex_queue"):
func notifyDelegate() {
serialQueue.async {
// notify delegate
}
}
If I run the delegate notification as an asynchronous task in a different queue, then everything works as expected. I couldn't find any note on Apple's documentation on why calling an asynchronous task inside the same queue causes a deadlock.
You can’t call serialQueue.sync
from the block that is being executed by the serialQueue
.
TL;DR;
Here is what I think is likely happening:
serialQueue.async
from notifyDelegate
.changeState
, incorrectly assuming that current thread is not the serialQueue
’s thread.changeState
method, being on the serialQueue
’s call stack, you schedule synchronously another block B via serialQueue.sync
which can never start because you wait for it to be started in the previously asynchronously scheduled block A which is currently executed by the serialQueue
.Ways to avoid this situation:
OR
os_unfair_lock
or NSLock
or NSRecursiveLock
instead. It might also improve the performance.