Here I create concurrent queue with .background priority:
let background = DispatchQueue(label: "backgroundQueue",
qos: .background,
attributes: [],
autoreleaseFrequency: .inherit,
target: nil)
When I'm trying to call DispatchQueue.main.sync
from this queue asynchronously it executes successfully
background.async {
DispatchQueue.main.sync {
print("Hello from background async")
}
}
However, if I'm trying to call DispatchQueue.main.sync
from this queue synchronously it causes deadlock
background.sync {
DispatchQueue.main.sync {
print("Hello from background sync")
}
}
Why calling DispatchQueue.main.sync
asynchronously from concurrent queue succeeds but synchronously fails?
CodePudding user response:
There are two types of DispatchQueue:
- Serial Queue - A work item starts to be executed once the previous one has finished execution
- Concurrent Queue - Work items are executed concurrently
It has also two dispatching techniques:
- sync - it blocks the calling thread until execution doesn’t finish(your code waits until that item finishes execution)
- async - it doesn’t block the calling thread and your code continues executing while the work item runs elsewhere
Note: Attempting to synchronously execute a work item on the main queue results in a deadlock.
For Apple documentation: https://developer.apple.com/documentation/dispatch/dispatchqueue
CodePudding user response:
Quoting apple docs
.sync
This function submits a block to the specified dispatch queue for synchronous execution. Unlike dispatch_async(::), this function does not return until the block has finished
Which means when you first called background.sync {
control was on main thread which belongs to a main queue (which is a serialized queue), as soon as the statement background.sync {
was executed, controlled stopped at main queue and its now waiting for the block to to finish execution
But inside background.sync {
you access the main queue again by referring DispatchQueue.main.sync {
and submit another block for synchronous execution which simply prints "Hello from background sync", but the control is already waiting on main queue to return from background.sync {
hence you ended up creating a deadlock.
Main Queue is waiting for control to return from background queue which in turn is waiting for Main queue to finish the execution of print statement :|
In fact apple specifically mentions this usecase in its Description
Calling this function and targeting the current queue results in deadlock.
Additional info:
By accessing main queue inside background queue you simply established circular dependency indirectly, if you really wanna test the above statement you can do it simply as
let background = DispatchQueue(label: "backgroundQueue",
qos: .background,
attributes: [],
autoreleaseFrequency: .inherit,
target: nil)
background.sync {
background.sync {
print("Hello from background sync")
}
}
Clearly you are referring background
queue inside background.sync
which will cause deadlock, which is what apple docs specifies in its description. Your case was slightly different in a sense that you referred to main queue causing the deadlock indirectly
How using async
in any one of those statements breaks the dealock?
Now you can use async
in either background.async {
or in DispatchQueue.main.async
and deadlock will break essentially (I am not suggesting which one is correct here, which is correct depends on your need and what are you trying to accomplish, but to break deadlock you can use async
in any one of those dispatch statements and you will be fine)
I will just explain why deadlock will break in only one scenario ( You can infer the solution for other case obviously). Let's just say you use
background.sync {
DispatchQueue.main.async {
print("Hello from background sync")
}
}
Now main queue is waiting for the block to finish execution which you submitted to background queue for synchronous execution using background.sync
and inside background.sync
you access main queue again using DispatchQueue.main
but this time you submit your block for the asynchronous execution. Hence control will not wait for the block to finish the execution and instead returns immediately. Because there are no other statements in block you submitted to background queue, it marks the completion of task, hence control returns to main queue. Now main queue does processes tasks submitted and whenever it its time to process your print("Hello from background sync")
block it prints it.
CodePudding user response:
.sync
means it will block currently working thread, and wait until the closure has been executed. So your first .sync
will block the main thread (you must be executing the .sync in the main thread otherwise it won't be deadlock). And wait until the closure in background.sync {...}
has been finished, then it can continue.
But the second closure blocks the background thread and assign a new job to main thread, which has been blocked already. So these two threads are waiting for each other forever.
But if you switch you start context, like start your code in a background thread, could resolve the deadlock.
// define another background thread
let background2 = DispatchQueue(label: "backgroundQueue2",
qos: .background,
attributes: [],
autoreleaseFrequency: .inherit,
target: nil)
// don't start sample code in main thread.
background2.async {
background.sync {
DispatchQueue.main.sync {
print("Hello from background sync")
}
}
}
These deadlock is caused by .sync
operation in a serial queue. Simply call DispatchQueue.main.sync {...}
will reproduce the problem.
// only use this could also cause the deadlock.
DispatchQueue.main.sync {
print("Hello from background sync")
}
Or don't block the main thread at the very start could also resolve the deadlock.
background.async {
DispatchQueue.main.sync {
print("Hello from background sync")
}
}
Conclusion
.sync
operation in a serial queue could cause permanent waiting because it's a single threaded. It can't be stopped immediately and looking forward for a new job. The job it's doing currently should be done by first, therefore it can start another. That's why .sync
could not be used in a serial queue.