why concurrentQueue.sync DON'T cause deadlock - ios

This code will deadlock because:
they are in same thread
print(2) has to wait print(3)
print(3) has to wait print(2)
For example:
DispatchQueue.main.async {
print(Thread.current)
DispatchQueue.main.sync {
print(Thread.current)
print(2)
}
print(3)
}
Why in the concurrentQueue won't cause deadlock? They are also in same thread.
DispatchQueue.global().async {
print(Thread.current)
DispatchQueue.global().sync {
print(Thread.current)
print(2)
}
print(3)
}

You ask:
Why in the concurrentQueue won't cause deadlock? They are also in same thread.
No, they're necessarily in the same thread. They're in the same queue, but not necessarily the same thread. (As part of an optimization, see below, it actually might end up running on the same thread, but not necessarily so. But logically, you should think of it as running on a separate thread.)
This is the whole idea behind “concurrent queues” is that they can run individual dispatched tasks on different threads. That’s how they permit concurrent operation. One dispatched task on a concurrent queue can run on one thread while another dispatched task on that same queue can run on a separate thread.
As the old Concurrency Programming Guide says in its definition of a “concurrent queue”:
The currently executing tasks run on distinct threads that are managed by the dispatch queue.
Or, as the DispatchQueue documentation says:
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system. [emphasis added]
What makes this a little more confusing is that there is a GCD optimization that, if you are dispatching synchronously ...
As a performance optimization, this function executes blocks on the current thread whenever possible...
So, when dispatching synchronously from a queue, it actually can end up running that code on the same thread (unless you’re dispatching from a background queue to the main queue). Consider:
let queue = DispatchQueue.global()
let queue2 = DispatchQueue(label: "queue2")
queue.async {
print(1, Thread.current)
queue2.sync {
print(2, Thread.current)
}
print(3, Thread.current)
}
That second print statement will show that even though you have a completely different queue, it may, as part of the aforementioned sync optimization, run the code on the current thread. The same it true if that inner sync call was to the same queue to which the outer block was dispatched.
Now, when looking at the result of this optimization, it feels like this is just like the serial queue scenario, but it’s not. A serial queue only allows one dispatched task to run at a time, and thus the attempt to dispatch synchronously (blocking the current thread until the dispatched block runs) to itself is, by definition, a deadlock.
But a concurrent queue dispatching to itself will generally not deadlock. The one caveat is that the number of worker threads is fairly limited, so if you have “thread explosion” (where you might have more than 64 worker threads going), then it can deadlock, so make sure your limit your concurrency to some reasonable value.

Related

DispatchQueue.sync { } blocks "thread" or "queue"

I'm quite confused.
Code below will cause a deadlock for sure:
// Will execute
DispatchQueue.main.async { // Block 1
// Will execute
DispatchQueue.main.sync { // Block 2
// Will not be executed
}
// Will not be executed
}
Because
After we dispatch_async on main queue, it's submits the first block to the main queue to execute
At some moment, the system decides to run block1
The .sync method blocks "thread / queue?" <- My question
Because the "thread / queue" is blocked, block 2 can't execute before block 1 finishes, because Main queue is a serial queue, it execute task serially, one can't execute before another finishes
My question is: Does sync block current thread it's executing on or current queue? (I understand the difference between thread & queue)
Most answers on the internet says it blocks thread
If block thread -> How come the sync { } block can still execute since the thread is blocked?
If block queue -> Make more sense? Since the queue is blocked, we can't execute one before other finishes
I found some discussions about this:
dispatch_sync inside dispatch_sync causes deadlock
Difference Between DispatchQueue.sync vs DispatchQueue.async
What happens if dispatch on same queue?
You asked:
My question is: Does sync block current thread it's executing on or current queue?
It blocks the current thread.
When dealing with a serial queue (such as the main queue), if that queue is running something whose thread is blocked, that prevents anything else from running on that queue until the queue is free again. A serial queue can only use one thread at a time. Thus, dispatching synchronously from any serial queue to itself will result in a deadlock.
But, sync does not technically block the queue. It blocks the current thread. Notably, when dealing with a concurrent queue (such as a global queue or a custom concurrent queue), that queue can avail itself of multiple worker threads at the same time. So just because one worker thread is blocked, it will not prevent the concurrent queue from running another dispatched item on another, unblocked, worker thread. Thus, dispatching synchronously from a concurrent queue to itself will not generally deadlock (as long as you don’t exhaust the very limited worker thread pool).
E.g.
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async {
serialQueue.sync {
// will never get here; deadlock
print("never get here")
}
// will never get here either, because of the above deadlock
}
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
concurrentQueue.async {
concurrentQueue.sync {
// will get here as long as you don't exhaust the 64 worker threads in the relevant QoS thread pool
print("ok")
}
// will get here
}
You asked:
If block thread -> How come the sync { } block can still execute since the thread is blocked?
As you have pointed out in your own code snippet, the sync block does not execute (in the serial queue scenario).

GCD Serial Queue dispatch async and sync

I have some doubts regarding GCD.
Code snippet 1
serialQ.sync {
print(1)
serialQ.async {
print(2)
}
serialQ.async {
print(3)
}
}
Code snippet 2
serialQ.async {
print(1)
serialQ.async {
print(2)
}
serialQ.sync {
print(3)
}
}
I ran both of them in playground, and found that Code snippet 2 gives deadlock while Code snippet 1 runs fine. I have read a lot about GCD and started playing around with these concepts. Can anyone please provide a detailed explanation for the same ?
PS : serialQ is a serial Queue
According to my understanding,
Serial Queue - generates only one thread at a time, and once that thread is freed up then it is occupied or free to do other tasks
Serial Queue dispatched sync - blocks the caller thread from which the serial queue is dispatched and performs the tasks on that thread.
Serial Queue dispatched async - does'nt not blocks the caller thread, infact it runs on a different thread and keeps the caller
thread running.
But for the above query I am not able to get the proper explanation.
You are calling sync inside a block already executing on the same queue. This will always cause a deadlock. sync is the equivalent of saying “execute this now and wait for it to return.” Since you are already executing on that queue, the queue never becomes available to execute the sync block. It sounds like you’re looking for recursive locks, but that’s not how queues work. It’s also quite arguably an anti-pattern in general. I discuss this more in this answer: How to implement a reentrant locking mechanism in objective-c through GCD?
EDIT: Came back to add some thoughts on your "understandings":
Serial Queue - generates only one thread at a time, and once that thread is freed up then it is occupied or free to do other tasks
A serial queue doesn't "generate" one thread. Queues and threads are different things and have different semantics. A serial queue requires one thread upon which to execute a work item, but there's not a one-to-one relationship between a serial queue and a thread. A thread is a relatively "heavy" resource and a queue is a relatively "light" resource. A single serial queue can execute work items on more than one thread over its lifetime (although never more than one thread at the same time). GCD maintains pools of threads that it uses to execute work items, but that is an implementation detail, and it's not necessary to understand how that's implemented in order to use queues properly.
Serial Queue dispatched sync - blocks the caller thread from which the serial queue is dispatched and performs the tasks on that thread.
A queue (serial or concurrent) is not "dispatched" (sync or otherwise). A work item is, well, enqueued into a queue. That work item will be subsequently executed by an arbitrary thread including, quite possibly, the calling thread. The guarantee is that only one work item enqueued to a given serial queue will be executing (on any thread) at one time.
Serial Queue dispatched async - doesn't block the ~caller~ enqueueing thread, in fact it runs on a different thread and keeps the caller thread running. (minor edits for readability)
This is close, but not quite accurate. It's true that enqueueing a work item onto a serial queue with async does not block the enqueueing thread. It's not necessarily true that the work item is executed by a different thread than the enqueueing thread, although in the common case, that is usually the case.
The thing to know here is that the difference between sync and async is strictly limited to the behavior of the enqueueing thread and has no (guaranteed) bearing or impact on which thread the work item is executed. If you enqueue a work item with sync the enqueueing thread will wait (possibly forever, in the specific case you outlined here) for the work item to complete, whereas if you enqueue a work item with async the enqueueing thread will continue executing.
A sync call, as you point out, blocks the current thread until the block runs. So when you make sync to the same serial queue that you’re currently on, you are blocking the queue, waiting for a block to run on the same queue that you just blocked, resulting in a deadlock.
If you really want to run something synchronously on the current queue, don’t dispatch it with sync at all and just run it directly. E.g.:
serialQ.async {
print(1)
serialQ.async {
print(2)
}
// serialQ.sync { // don't dispatch synchronously to the current serial queue
print(3)
// }
}
Or dispatch asynchronously. E.g.,
serialQ.async {
print(1)
serialQ.async {
print(2)
}
serialQ.async {
print(3)
}
}
Or use a concurrent queue (in which case you have to be careful to make sure you don’t have thread explosion, which could result in deadlock, too). E.g.,
let concurrentQ = DispatchQueue(label: "...", attributes: .concurrent)
concurrentQ.async {
print(1)
concurrentQ.async {
print(2)
}
concurrentQ.sync {
print(3)
}
}

What happens if dispatch on same queue?

I'd like to understand for below case if it's needed to check whether callbackQueue is current queue.
Please help me clear these scenarios, about what could happen if current queue is callback queue:
callbackQueue is main queue.
callbackQueue is concurrent queue.
callbackQueue is serial queue.
- (void)fetchWithCallbackQueue:(dispatch_queue_t)callbackQueue
{
dispatch_async(callbackQueue, ^{
});
}
I highly recommend you to watch these videos. Then go through the examples I provided and then change the code and play around with them as much as you can. It took me 3 years to feel fully comfortable with iOS multi-threading so take your time :D
Watch the first 3 minutes of this RWDevCon video and more if you like.
Also watch 3:45 until 6:15. Though I recommend you watch this video in its entirety.
To summarize the points the videos make in the duration I mentioned:
threading and conccurrency is all about the source queue and destination. queue.
sync vs. async is specifically a matter of the source queue.
Think of source and destination queues of a highway where your work is being done.
If you do async, then it's like you sending a car (has to deliver stuff) exiting the highway and then continue to let other cars drive in the highway.
If you do sync, then it's like you sending a car (has to deliver stuff) exiting the highway and then halting all other cars on the highway until the car delivers all its stuff.
Think of a car delivering stuff as a block of code, starting and finishing execution.
What happens for main queue is identical to what happens for serial queue. They're both serial queues.
So if you're already on main thread and dispatch to main thread and dispatch asynchronously then, anything you dispatch will go to the end of the queue
To show you what I mean: In what order do you think this would print? You can easily test this in Playground:
DispatchQueue.main.async {
print("1")
print("2")
print("3")
DispatchQueue.main.async {
DispatchQueue.main.async {
print("4")
}
print("5")
print("6")
print("7")
}
print("8")
}
DispatchQueue.main.async {
print("9")
print("10")
}
It will print:
1
2
3
8
9
10
5
6
7
4
Why?
It's mainly because every time you dispatch to main from main, the block will be placed at the end of the main queue.
Dispatching to main while you're already on the main queue is very hidden subtle reason for many tiny delays that you see in an app's user-interaction.
What happens if you dispatch to the same serial queue using sync?
Deadlock! See here
If you dispatch to the same concurrent queue using sync, then you won't have a deadlock. But every other thread would just wait the moment you do sync. I've discussed that below.
Now if you're trying to dispatch to a concurrent queue, then if you do sync, it's just like the example of the highway, where the entire 5 lane highway is blocked till the car delivers everything. But it's kinda useless to do sync on a concurrent queue, unless you're doing something like a .barrier queue and are trying to solve a read-write problem.
But to just see what happens if you do sync on a concurrent queue:
let queue = DispatchQueue(label: "aConcurrentQueue", attributes: .concurrent)
for i in 0...4 {
if i == 3 {
queue.sync {
someOperation(iteration: UInt32(i))
}
} else {
queue.async {
someOperation(iteration: UInt32(i))
}
}
}
func someOperation(iteration: UInt32) {
sleep(1)
print("iteration", iteration)
}
will log:
'3' will USUALLY (not always) be first (or closer to the first), because sync blocks get executed on the source queue. As docs on sync say:
As a performance optimization, this function executes blocks on the current thread whenever possible
The other iterations happen concurrently. Each time you run the app, the sequence may be different. That's the inherit unpredictability associated with concurrency. 4 will be closer to being completed last and 0 would be closer to being finished sooner. So something like this:
iteration 3
iteration 0
iteration 2
iteration 1
iteration 4
If you do async on a concurrent queue, then assuming you have a limited number of concurrent threads, e.g. 5 then 5 tasks would get executed at once. Just that each given task is going to the end of the queue. It would make sense to do this for logging stuff. You can have multiple log threads. One thread logging location events, another logging purchases, etc.
A good playground example would be:
let queue = DispatchQueue(label: "serial", attributes: .concurrent)
func delay(seconds: UInt32 ) {
queue.async {
sleep(seconds)
print(seconds)
}
}
for i in (1...5).reversed() {
delay(seconds: UInt32(i))
}
Even though you've dispatched the 5 first, this would print
1
2
3
4
5
In your example with dispatch_async (or just async in Swift), it doesn’t matter. The dispatched block will simply be added to the end of the relevant queue and will run asynchronously whenever that queue becomes available.
If, however, you used dispatch_sync (aka sync in Swift), then suddenly problems are introduced if you dispatch from a serial queue back to itself. With a “synchronous” dispatch from a serial queue to itself, the code will will “deadlock”. (And because the main queue is a serial queue, synchronous dispatches from main queue to itself manifest the same problem.) The dispatch_sync says “block the current thread until the designated queue finishes running this dispatched code”, so obviously if any serial queue dispatches synchronously back to itself, it cannot proceed because it’s blocking the queue to which you’ve dispatched the code to run.
Note that any blocking GCD API, such as dispatch_semaphore_wait and dispatch_group_wait (both known as simply wait in Swift), will suffer this same problem as the synchronous dispatch if you wait on the same thread that the serial queue uses.
But, in your case, dispatching asynchronously with dispatch_async, you shouldn’t have any problems.

Why we use async on main thread

Updating UI on a thread other than the main thread is a common mistake that can result in missed UI updates, visual defects, data corruptions, and crashes.
https://developer.apple.com/documentation/code_diagnostics/main_thread_checker
Example:
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
DispatchQueue.main.async { // Correct
self.label.text = "\(data.count) bytes downloaded"
}
}
}
task.resume()
My question starts here -
I am confused with above statement when we say .async means not simultaneously (Or not parallel) with .main. Can someone explain my problem?
DispatchQueue.main.async means you queue up a task in the main queue, without waiting the task to be executed. The main queue tasks will be run on the main thread one by one automatically, scheduled by the OS.
Think of each DispatchQueue as a worker. Calling .async adds a task under the worker's TODO list and do not wait for the worker to finish the task. DispathQueue.main is the specific worker that work on the main thread.
Oh the other hand, .sync will block the thread until the task block has finished executing. You can call .sync on any thread other than the main thread since main thread must not be blocked.
That doesn't means you cannot call DispatchQueue.main.sync. You can call DispatchQueue.main.sync just like any custom dispathQueue.sync on non- main thread.
e.g.
DispatchQueue(label: "bgqueue", qos: .background).async
{
DispatchQueue.main.sync{}
}
is OK.
But
DispatchQueue.main.async{
DispatchQueue.main.sync{}
}
is NOT.
.sync is usually not quite useful. If you want something to happen after a main queue task, you just queue that "something" into the main queue too. It is not worth to block a thread if not necessary.
That being said, here are two rules to remember when using .sync, regardless of which queue is receiving the .sync call :
never call .sync from a queue to itself, which causes deadlock.
never call .sync from main queue, which blocks the UI thread.
I think you are confused how DispatchQueue works.
DispatchQueue simply manages thread pool, and when we give it a block of code to execute it simply picks an idle thread and run that piece of code on it.
So basically one thread can be used by many queues. A queue is simply a task list which manages all the tasks which will execute in future.
So basically here when you are doing DispatchQueue.main.async then you are simply instructing main queue to execute your code without waiting for pending tasks execution.

main.async vs main.sync() vs global().async in Swift3 GCD

Example A:- This cause App Crash.
DispatchQueue.main.async {
let url = URL(string: imageUrl)
do {
let data = try Data(contentsOf: url!)
DispatchQueue.main.sync {
self.imageIcon.image = UIImage(data: data)
}
}
Example B:- But This don't
DispatchQueue.global().async {
let url = URL(string: imageUrl)
do {
let data = try Data(contentsOf: url!)
DispatchQueue.main.sync {
self.imageIcon.image = UIImage(data: data)
}
}
As per my knowledge,
x.sync means doing thing in main thread/UI thread and x.async means
doing in background thread.
Global means performing something with concurrent queue i.e Parallel
task.
Quest1:- So why does my app crashed when i performed task in background thread i.e main.async and than call main thread to update UI.
Quest2:- Is there any difference in main.async & global().async.
In simple term i come to conclusion that -
Queue- There are 3 Types of Queue i.e. 1 Main Queue, 4 Global Queue and Any No. of Custom Queues.
Threads- One is Main Thread and other background threads which system
provides to us.
DispatchQueue.main.async
-It means performing task in main queue with using of background thread (w/o blocking of UI) and when task finish it automatic Updated to UI because its already in Main Queue.
DispatchQueue.global().async along with global().sync
It means performing task in Global Queue with using of background thread and when task finish, than global().sync use bring the work from globalQueue to mainQueue which update to UI.
Reason of My App Crash
I was trying to bring the completed task to MainQueue by using(main.sync), but it was already on MainQueue because i hadnt switched the Queue, and this create DeadLock (MainQueue waiting for itself), causes my app crash
GCD
Thread -> GCD -> Operation + OperationQueue(life cycle, dependencies between different queues, cancel)
[Sync vs Async]
[iOS Thread safe]
Grand Central Dispatch GCD libdispatch operates on dispatch queues DispatchQueue with a FIFO order
DispatchQueue.<queue>.<sync/async> means run a <sync/async> task on the <queue>
GCD supports:
global queue - shared between whole iOS operation system
private queue
main - global serial queue on a main thread which is used to working with UI
DispatchQueue.main
global() - global concurrent.
DispatchQueue.global()
DispatchQueue.global(qos: .background)
Custom queue: - private serial\concurrent queue
init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
DispatchQueue(label: "serialQueue") // without attributes
DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
QUEUE
Usually when we talk about concurrent we talk about queues. Count of threads are depends on OS conditions. There are no run loop[About] for worker thread.
concurrent has different groups of queues with priorities(main thread, hight, default, low, background) which you pass at the task using qos QoSClass(Quality of Service) (priority GlobalQueuePriority is deprecated):
userInteractive - the highest priority. It can be used for very fast calculation which immediately are reflected on UI. For example animation calculations
userInitiated - high priority queue - relevant for UI. Up to several seconds. For example loading data for showing on UI
default - default priority queue
utility - low priority queue - up to several minutes like working with big data like images, processing...
background - background priority queue - up to several hours while app is on background like sync data.
sync/async
sync - block a current thread and wait when it will be finished on a specified queue
async - do not block a current thread and send an execution block of code to the specificified queue
Common mistake: deadlock
If you call DispatchQueue.main.sync on a main thread - the app will be frozen because the calling DispatchQueue.main.sync starts waiting immediately when the dispatched block is finished (dispatched block is not started)
Some notes:
DispatchWorkItem - delaying/cancelling/prioritise a task inside Queue or DispatchGroup
DispatchGroup if you are going to execute several async tasks with a single callback even on different queues. All these task should be grouped. DispatchGroup contains thread safe counter and when it equals 0 notify is called
//create group
let group = DispatchGroup()
//case 1
DispatchQueue.<queue>.async(group: group) //
//case 2 - manual
group.enter() //<- +1
DispatchQueue.global().async {
//logic
group.leave() //<- -1
}
//notification
group.notify(queue: <callback_queue>) {
//logic
}
Barrier flag inside concurrent queue for sync/async task guaranties that there is no race condition[About]. The best place for it is custom queue because does not block any others global tasks:
customQueue.async(flags: .barrier) {
//logic
someProperty = someValue
}
all task which were started are finished
Single Barrier task
Executing all other tasks in the queue
thread safe operation can be reached through Barrier in concurrent queue for shared variable:
read - sync operation on concurrent queue
write - async operation with barrier
[Thread safe singleton]
In first case, you run the code on main and then you use main.sync on the main thread. In essence, you are trying to tell the main queue to wait for itself - which is obviously nonsense and therefore it causes crash.
In the second case, you run the code on the background thread, and then you use main.sync to wait until the main thread can run the block provided in main.sync.
In general, I would use async and not sync all the time, unless sync is necessary - and always sync one thread (DispatchQueue) with a different one, never with the same one.
You were mixing up the terms sync/async and main/global.
Sync - Run some task synchronously (i.e. the thread which can be main/global/any other thread will wait for the task to complete)
Async - Run some task asynchronously (i.e. the thread which can be main/global/any other thread will push the task to a queue and continue executing next steps outside your block. It wont wait)
Now lets go one by one in your code which was crashing :
Lets put some names for our threads so it will be easy for our understanding:
1) ThreadA - Which encounters your dispatch statements (this can also be Main thread, but for explanation purpose I feel its better)
2) ThreadB - Global thread which gets created when you submit some task.
3) ThreadMain - Main thread
Example A:
DispatchQueue.main.async - ThreadA comes and execute this statement and put your block on ThreadMain and moves on (since its async) to next steps after the block. Now lets talk about ThreadMain, what it will do from here. Since ThreadMain got a block (submitted by ThreadA) it starts executing step by step and suddenly it sees 'DispatchQueue.main.sync' and submits the inner block on to the same TheradMain queue and keeps onnnnn waitingggggg (since its sync). So literally you are making the ThreadMain into deadlock situation.

Resources