How do I create a deadlock in Grand Central Dispatch?

17,641

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.

Share:
17,641

Related videos on Youtube

BlackMouse
Author by

BlackMouse

Updated on September 16, 2022

Comments

  • BlackMouse
    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
      Vladimir about 11 years
      You want example of code that creates deadlock?
    • BlackMouse
      BlackMouse about 11 years
      Yes please, for learning
    • zoul
      zoul about 11 years
      See also this question for a realistic example that can easily deadlock.
  • BlackMouse
    BlackMouse about 11 years
    When in deadlock... shouldn't the UI be unresponsive?
  • David Rönnqvist
    David Rönnqvist about 11 years
    @user1251004 Only if the main queue is being blocked.
  • Joris Kluivers
    Joris Kluivers about 11 years
    In this example only the created queue is blocked. The main queue happily continues to run.
  • Sushree Swagatika
    Sushree Swagatika over 6 years
    Are there any other possible ways to create a deadlock in iOS?
  • ScottyBlades
    ScottyBlades about 6 years
    Why Is the inner block waiting to start until the outer block finishes?
  • Johnykutty
    Johnykutty almost 6 years
    @ScottyBlades because its a serial queue
  • ScottyBlades
    ScottyBlades almost 6 years
    ok. 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....
    Mansuu.... over 4 years
    this gives me EXC_BAD_INSTRUCTION at line number 3
  • Ricardo
    Ricardo almost 4 years
    What happens exactly if I use a concurrent queue? Thanks a lot @JorisKluivers
  • Khuong
    Khuong about 3 years
    @Ricardo it will run normally if you use a concurrent queue in this case.
  • Laur Stefan
    Laur Stefan almost 2 years
    Yes, 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.