4

I just read this on objc.io Going Fully Asynchronous but can't find good explanation

dispatch_queue_t queueA; // assume we have this
dispatch_sync(queueA, ^(){  // (a)
    dispatch_sync(queueA, ^(){ // (b)
        foo();
    });
});

Once we hit the second dispatch_sync we’ll deadlock: We can’t dispatch onto queueA, because someone (the current thread) is already on that queue and is never going to leave it.

As long as I understand

  1. dispatch_sync just add the work item (I avoid using the word "block" as it may confuse) to the queueA, then this work item will be send to queueA 's target queue, then GCD will preserve a thread threadWorkItem for this work item
  2. When I reach (b), I'm in the thread threadWorkItem (suppose threadWorkItem is the name of this thread), so I think enqueuing another work item to queueA is no problem. But some people say that at this time, queueA is preserved, queueA is blocked -> causes deadlock, which confuses me

I already read many threads related to this, such as Deadlock with dispatch_sync, Why can't we use a dispatch_sync on the current queue?, Why is this dispatch_sync() call freezing?, ... but can't find good explanation. Some say dispatch_sync blocks the queue, some say it blocks the current thread, ... :(

So why does it cause deadlock ?

Community
  • 1
  • 1
onmyway133
  • 45,645
  • 31
  • 257
  • 263

2 Answers2

13

The dispatch_sync blocks the current thread until the dispatched code finishes, and if you're dispatching synchronously from a serial queue, you therefore are effectively blocking the queue, too. So if you synchronously dispatch from serial queue to itself, it results in a deadlock.

But to be clear, dispatch_sync blocks the current thread, not the current queue. When dealing with a concurrent queue, a different worker thread will be used for the subsequently dispatched block and no deadlock results.

You appear to be responding to a discussion at the end of the Dispatch Queues chapter of the Concurrency Programming Guide, which says:

Do not call the dispatch_sync function from a task that is executing on the same queue that you pass to your function call. Doing so will deadlock the queue. If you need to dispatch to the current queue, do so asynchronously using the dispatch_async function.

That is not entirely correct. If (a) you are doing this with a concurrent queue; and (b) there are available worker threads, this will not cause a deadlock. But it's a bad practice and should be avoided, nonetheless.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1. so the problem arises if the thread (which is use to call the 1st dispatch_sync) and the thread (which GCD will use to run the work item for 2nd dispatch_sync) is **the same**? It hardly happens because finally all work items will go into the global queues, and GCD will take thread from the threadpool, which hardly makes this **the same** ?!! 2. Even if this is **the same**, I think 2nd dispatch_sync just send the work item to queueA, and at that point there is no thread involved ?!! – onmyway133 May 29 '14 at 18:13
  • in 2. I mean at that time, the work item is just sent to queueA and is not executed immediately, it will be run in future run loop. So at that time, there is no thread involved – onmyway133 May 29 '14 at 18:36
  • in your 2nd comment "For serial queue, dispatch_sync will block the thread" what thread is this? The thread used to call the 1st dispatch_sync or the thread in which the work item of 1st dispatch_sync runs on ? – onmyway133 May 30 '14 at 03:08
  • 4
    @entropy `dispatch_sync` always blocks the thread from which it was called, waiting for the dispatched block of code to finish. If a block of code already dispatched to some serial queue, itself attempts to synchronously dispatch another block of code to the same queue, the second dispatched block of code will never commence, because it cannot start until the first block finishes. But that first block is blocked as a result of its having called `dispatch_sync`. – Rob May 30 '14 at 03:42
1

Use this code to make calls from any thread to main thread without risk of deadlocking. Please keep in mind if there is deep hierarchy of queues, you can still get deadlocked.

static inline void dispatch_synchronized (dispatch_queue_t queue,
                                          dispatch_block_t block)
{
    dispatch_queue_set_specific (queue, (__bridge const void *)(queue), (void *)1, NULL);
    if (dispatch_get_specific ((__bridge const void *)(queue)))
        block ();
    else
        dispatch_sync (queue, block);
}
Dvole
  • 5,725
  • 10
  • 54
  • 87