How do I create a deadlock in Grand Central Dispatch?
Solution 1
An intentional deadlock on a certain queue:
dispatch_queue_t queue = dispatch_queue_create("my.label", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
// outer block is waiting for this inner block to complete,
// inner block won't start before outer block finishes
// => deadlock
});
// this will never be reached
});
It's clear here that the outer and inner blocks are operating on the same queue. Most cases where this will occur is in places where it's less obvious what queue the caller of the dispatch_sync
is operating on. This usually occurs in a (deeply) nested stack where you're executing code in some class that was originally launched on a certain queue, and by accident you call a dispatch_sync
to the same queue.
Solution 2
Simple code that creates deadlock:
dispatch_queue_t q = dispatch_queue_create("deadlock queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(q, ^{
NSLog(@"2");
dispatch_sync(q, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
Log output:
1
5
2
Here internal block is scheduled to be run on serial queue q
but it cannot run until current block is finished, while current block, in turn, waits internal to finish as we called it synchronously.
Solution 3
The simplest way to block is to dispatch_sync
on the current queue:
dispatch_sync(dispatch_get_current_queue(), ^{});
This blocks when the current queue is a serial queue, for example the main queue.
Solution 4
In latest Swift syntax:
let queue = DispatchQueue(label: "label")
queue.async {
queue.sync {
// outer block is waiting for this inner block to complete,
// inner block won't start before outer block finishes
// => deadlock
}
// this will never be reached
}
Solution 5
Interviewers often ask: "What is the simplest way to cause a deadlock?"
Obj-C:
dispatch_sync(dispatch_get_main_queue(), ^{});
Swift:
DispatchQueue.main.sync {}
Calling sync
from the main thread will cause a deadlock because the main queue is a serial queue and sync
stops current queue execution until passed block/closure has finished.
Related videos on Youtube
BlackMouse
Updated on September 16, 2022Comments
-
BlackMouse over 1 year
In Apple docs, it says:
Important: You should never call the dispatch_sync or dispatch_sync_f function from a task that is executing in the same queue that you are planning to pass to the function. This is particularly important for serial queues, which are guaranteed to deadlock, but should also be avoided for concurrent queues.
How do you write the code to do exactly this?
-
Vladimir about 11 yearsYou want example of code that creates deadlock?
-
BlackMouse about 11 yearsYes please, for learning
-
zoul about 11 yearsSee also this question for a realistic example that can easily deadlock.
-
-
BlackMouse about 11 yearsWhen in deadlock... shouldn't the UI be unresponsive?
-
David Rönnqvist about 11 years@user1251004 Only if the main queue is being blocked.
-
Joris Kluivers about 11 yearsIn this example only the created
queue
is blocked. The main queue happily continues to run. -
Sushree Swagatika over 6 yearsAre there any other possible ways to create a deadlock in iOS?
-
ScottyBlades about 6 yearsWhy Is the inner block waiting to start until the outer block finishes?
-
Johnykutty almost 6 years@ScottyBlades because its a serial queue
-
ScottyBlades almost 6 yearsok. got it. The async block is added to the queue, and as it is attempted to be executed, it will hit the sync function which will not start until the async block is finished executing which will never happen because the block needs to reach the end of its execution which will never happen because the queue.sync is waiting on the other.
-
Mansuu.... over 4 yearsthis gives me EXC_BAD_INSTRUCTION at line number 3
-
Ricardo almost 4 yearsWhat happens exactly if I use a concurrent queue? Thanks a lot @JorisKluivers
-
Khuong about 3 years@Ricardo it will run normally if you use a concurrent queue in this case.
-
Laur Stefan almost 2 yearsYes, because the concurent queue creates Threads for each task you add using the .sync{...}, .async{...} methods, or reuse the current threads to add the new task - this is something that the system does. So when you add Task1 using async the system creates Thread1, and Thread2 for the sync op. By calling sync inside async you are locking Thread1 until the op ends. For a deadlock test you can try to make it 3 lvls deep with async->sync->sync.